Search code examples
c#multithreadingmanualreseteventwaithandle

How to use ManualResetEvent to replace boolean flags in this class


I have made a previous question with the same code, and have been advised to use ManualResetEvent, because it is the right way of doing what I want, and I agree with that.

Problem is: I have read and re-read the docs and a lot of tutorials about ManualResetEvent, with its WaitOne, Set, Unset and Reset methods, but frankly I didn't quite understand how they are supposed to be used.

What my code does: it keeps looking for connected devices, and when it finds one, it keeps verifying if it's still connected (otherwise, start looking for again). This is the "monitoring" activity, which can be started or stopped by client code using Start and Stop methods. There is also a _busy flag, so that Stop method only returns after one monitoring cycle is complete.

Fact is: currently the bool flag approach is not working, so I want to replace it with ManualResetEvent approach, but cannot figure out even how to start.

  • Should I replace the flags by ManualResetEvents, one-to-one?
  • Should SearchDevices() and MonitorDeviceConnection() methods conditionally run in the same thread, or should each one have (or be) its own thread?
  • How the difference between Start and Stop (turning on and off, called from client code) and "selecting" between both monitoring methods affect the way each ManualResetEvent is used? (not quite sure this question makes much sense)
  • Using an enum flag to select one of two possible paths of execution is quite a code smell, isn't it? What would be a sensible way of getting rid of it in a "ManualResetEvent context"?

Here's the code:

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
    }
}    

Solution

  • There's a difference in ManualResetEvent and AutoResetEvent. You always have to reset the ManualResetEvents. you can imagine them like Manual door closing and Auto door closing when you enter in a room. For ManualResetEvent you have to manually call the Reset() other wise the thread would keep on running unless you call the Reset.

    I have tried to create a simulator for your problem using the AutoResetEvent. Hope this will give you more clear picture. Later if you want you can try changing and using ManualResetEvent as you may.

    If you look at the Idea of this implementation then it works like this: Thread working with AutoResetEvent

    And this sequence goes on for switching states if necessary.

    In this sample I'll be using 2 threads one for search device and other for Monitoring the device status. The RulyCanceler will be your token for cancellation. This would be the replacement of _busy flag that you have used.

    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();
            }
        }
    }
    

    If you look at the Stop() method this method is using the CancelToken to send an interrup signal. WakeSleepThread is used to wake the any of the sleeping thread out of two.

    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();
            }
    

    I have used above simultion to change the device status and device object to set as null. If you run the program you'll see the output something like this.

    Workflow of thread simulations

    Note: There might be areas to improve this sample code. Feel free to optimize as you may.

    References - http://www.albahari.com/threading/