Как использовать ManualResetEvent для замены логических флагов в этом классе
Я сделал предыдущий вопрос с тем же кодом, и мне посоветовали использовать ManualResetEvent
потому что это правильный способ делать то, что я хочу, и я согласен с этим.
Проблема в том, что я прочитал и перечитал документы и много уроков по ManualResetEvent
, с этими WaitOne
, Set
, Unset
а также Reset
методы, но, честно говоря, я не совсем понял, как они должны использоваться.
Что делает мой код: он продолжает искать подключенные устройства, а когда находит одно, он продолжает проверять, все еще ли он подключен (в противном случае, начните искать снова). Это действие "мониторинга", которое может быть запущено или остановлено клиентским кодом с помощью Start
а также Stop
методы. Также есть _busy
флаг, чтобы Stop
метод возвращается только после завершения одного цикла мониторинга.
Факт: в настоящее время подход с использованием флага bool не работает, поэтому я хочу заменить его на ManualResetEvent
подход, но не могу понять даже, как начать.
- Должен ли я заменить флаги на ManualResetEvents, один на один?
- Должен
SearchDevices()
а такжеMonitorDeviceConnection()
методы условно выполняются в одном и том же потоке, или каждый из них должен иметь (или быть) собственный поток? - Как разница между
Start
а такжеStop
(включение и выключение, вызов из клиентского кода) и "выбор" между обоими методами мониторинга влияют на способ использования каждого ManualResetEvent? (не совсем уверен, что этот вопрос имеет много смысла) - Использование флага enum для выбора одного из двух возможных путей выполнения - это настоящий запах кода, не так ли? Что было бы разумным способом избавиться от него в "
ManualResetEvent
контекст "?
Вот код:
public class DeviceMonitor
{
bool _running;
bool _monitoring;
bool _busy = false;
MonitoringMode _monitoringMode;
Thread _monitoringThread;
readonly object _lockObj = new object();
// CONSTRUTOR
public DeviceMonitor()
{
_monitoringThread = new Thread(new ThreadStart(ExecuteMonitoring));
_monitoringThread.IsBackground = true;
_running = true;
_monitoringThread.Start();
}
public void Start()
{
_monitoring = true;
}
public void Stop()
{
_monitoring = false;
while (_busy)
{
Thread.Sleep(5);
}
}
void ExecuteMonitoring()
{
while (_running)
{
Console.WriteLine("ExecuteMonitoring()");
if (_monitoring)
{
lock (_lockObj)
{
_busy = true;
}
Console.WriteLine("busy");
if (_monitoringMode == MonitoringMode.SearchDevices)
{
SearchDevices();
}
else
if (_monitoringMode == MonitoringMode.MonitorDeviceConnection)
{
MonitorDeviceConnection();
}
lock (_lockObj)
{
_busy = false;
}
Console.WriteLine("not busy");
}
Thread.Sleep(1000);
_busy = false;
}
}
private void SearchDevices()
{
var connected = ListDevices();
if (connected.Count > 0)
{
Device = connected.First();
ToggleMonitoringMode();
}
else
Device = null;
}
void MonitorDeviceConnection()
{
if (Device == null)
{
ToggleMonitoringMode();
}
else
{
bool responding = Device.isConnected;
Console.WriteLine("responding " + responding);
if (!responding)
{
Device = null;
ToggleMonitoringMode();
}
}
}
void ToggleMonitoringMode()
{
if (_monitoringMode == MonitoringMode.SearchDevices)
_monitoringMode = MonitoringMode.MonitorDeviceConnection;
else
if (_monitoringMode == MonitoringMode.MonitorDeviceConnection)
_monitoringMode = MonitoringMode.SearchDevices;
}
enum MonitoringMode
{
SearchDevices,
MonitorDeviceConnection
}
}
1 ответ
Есть разница в ManualResetEvent
а также AutoResetEvent
, Вы всегда должны сбросить ManualResetEvents
, Вы можете представить их как ручное закрытие дверей и автоматическое закрытие дверей при входе в комнату. За ManualResetEvent
Вы должны вручную позвонить Reset()
в противном случае поток продолжит работать, пока вы не вызовете Reset.
Я попытался создать симулятор для вашей проблемы, используя AutoResetEvent
, Надеюсь, что это даст вам более четкую картину. Позже, если вы хотите, вы можете попробовать изменить и использовать ManualResetEvent
как вы можете.
Если вы посмотрите на идею этой реализации, то она работает так:
И эта последовательность продолжается для переключения состояний при необходимости.
В этом примере я буду использовать 2 потока, один для поиска устройства, а другой - для мониторинга состояния устройства. RulyCanceler
будет вашим токеном для отмены. Это будет замена _busy
флаг, который вы использовали.
public class DeviceMonitorSignaling
{
readonly object _lockObj = new object();
EventWaitHandle searchingHandle;
EventWaitHandle monitoringHandle;
bool _running;
bool _monitoring;
volatile Device device;
MonitoringMode _monitoringMode;
Thread _monitoringThread;
Thread _searchDeviceThread;
RulyCanceler CancelToken;
// CONSTRUTOR
public DeviceMonitorSignaling()
{
CancelToken = new RulyCanceler();
searchingHandle = new AutoResetEvent(false);
monitoringHandle = new AutoResetEvent(false);
_monitoringThread = new Thread
(() =>
{
try { MonitorDeviceConnection(CancelToken); }
catch (OperationCanceledException)
{
Console.WriteLine("Canceled Search!");
}
});
_searchDeviceThread = new Thread(() =>
{
try { SearchDevices(CancelToken); }
catch (OperationCanceledException)
{
Console.WriteLine("Canceled Monitor!");
}
});
_monitoringThread.IsBackground = true;
}
public void Start()
{
_monitoring = true;
_running = true;
_searchDeviceThread.Start();
_monitoringThread.Start();
}
public void Stop()
{
CancelToken.Cancel();
// One of the thread would be sleeping to identify and recall it.
WakeSleepingThread();
}
/// <summary>
/// Method to search the device.
/// </summary>
/// <param name="cancelToken"></param>
void SearchDevices(RulyCanceler cancelToken)
{
while (_running)
{
cancelToken.ThrowIfCancellationRequested();
Console.WriteLine("Searching devices....");
Thread.Sleep(1000);
device = new Device(); // may be some logic to detect the device.
Console.WriteLine("Finished!!! Searching devices. Start monitoring.");
if(device != null)
{
// Block the search thread and start the monitoring thread.
ToggleMonitoringMode();
}
}
}
/// <summary>
/// Once the device is detected
/// </summary>
/// <param name="cancelToken"></param>
void MonitorDeviceConnection(RulyCanceler cancelToken)
{
monitoringHandle.WaitOne();
Console.WriteLine("monitoring started.");
while (_monitoring)
{
cancelToken.ThrowIfCancellationRequested();
Thread.Sleep(1000);
if (device == null)
{
Console.WriteLine("Disconnected Invoking search.");
// Block monitoring thread and awake the device search.
ToggleMonitoringMode();
}
else
{
bool responding = device.isConnected;
Console.WriteLine("responding {0}", responding);
if (!responding)
{
Console.WriteLine("Not responding. Invoking search.");
device = null;
// Block monitoring thread and awake the device search.
ToggleMonitoringMode();
}
}
}
}
internal void ToggleMonitoringMode()
{
if (_monitoringMode == MonitoringMode.SearchDevices)
{
_monitoringMode = MonitoringMode.MonitorDeviceConnection;
monitoringHandle.Set();
searchingHandle.WaitOne();
}
else if (_monitoringMode == MonitoringMode.MonitorDeviceConnection)
{
_monitoringMode = MonitoringMode.SearchDevices;
searchingHandle.Set();
monitoringHandle.WaitOne();
}
}
internal void WakeSleepingThread()
{
if(_monitoringMode == MonitoringMode.MonitorDeviceConnection)
{
searchingHandle.Set();
}
else
{
monitoringHandle.Set();
}
}
enum MonitoringMode
{
SearchDevices,
MonitorDeviceConnection
}
/// <summary>
/// For test purpose remove the device.
/// </summary>
internal void DisconnectDevice()
{
if(device != null)
{
device = null;
}
}
/// <summary>
/// For test purpose change the device status
/// </summary>
internal void ChangeDeviceState()
{
if (device != null)
{
device.Disconnect();
}
}
/// <summary>
/// Dummy device
/// </summary>
internal class Device
{
public bool isConnected = false;
public Device()
{
isConnected = true;
}
public void Disconnect()
{
isConnected = false;
}
}
internal class RulyCanceler
{
object _cancelLocker = new object();
bool _cancelRequest;
public bool IsCancellationRequested
{
get { lock (_cancelLocker) return _cancelRequest; }
}
public void Cancel() { lock (_cancelLocker) _cancelRequest = true; }
public void ThrowIfCancellationRequested()
{
if (IsCancellationRequested) throw new OperationCanceledException();
}
}
}
Если вы посмотрите на метод Stop(), этот метод использует CancelToken для отправки сигнала прерывания. WakeSleepThread
используется, чтобы разбудить любого из спящего потока из двух.
static void Main(string[] args)
{
var obj = new DeviceMonitorSignaling();
Console.WriteLine("Starting...");
obj.Start();
Thread.Sleep(4000); // after 4 sec remove the device.
Console.WriteLine("Changing device state.");
obj.DisconnectDevice();
Thread.Sleep(4000); // // after 4 sec change the device status.
obj.ChangeDeviceState();
Console.Read();
Console.WriteLine("Stopping...");
obj.Stop();
Console.Read();
}
Я использовал вышеизложенную симуляцию для изменения статуса устройства и объекта устройства для установки на ноль. Если вы запустите программу, вы увидите что-то вроде этого.
Примечание. Могут быть области для улучшения этого примера кода. Не стесняйтесь оптимизировать, как вы можете.
Ссылки - http://www.albahari.com/threading/