Как использовать 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 как вы можете.

Если вы посмотрите на идею этой реализации, то она работает так: Тема, работающая с AutoResetEvent

И эта последовательность продолжается для переключения состояний при необходимости.

В этом примере я буду использовать 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/

Другие вопросы по тегам