When writing multi-threaded objects, execution speed is an important design consideration. Generally, to speed up the execution, the main thread that executes the operation logic is isolated from the event thread that issues the event. This isolation design prevents the main thread from being paused due to external event processing. To simplify thread management, threadpool can be used to complete the work of event threads. An example of a simple program is as follows:
namespace ConsoleApplication1{ class Program { static void Main(string[] args) { var obj = new ClassA(); obj.NotifyArrived += new Action<int>(obj_NotifyArrived); obj.Start(); Console.ReadLine(); obj.Stop(); } static void obj_NotifyArrived(int data) { Console.WriteLine(data); } } public class ClassA { // Fields private bool _isRunning = true; // Methods public void Start() { Thread thread = new Thread(this.Run); thread.Start(); } public void Stop() { _isRunning = false; } private void Run() { int i = 0; while (_isRunning == true) { Thread.Sleep(100); this.OnNotifyArrived(i++); } } // Events public event System.Action<int> NotifyArrived; private void OnNotifyArrived(int data) { WaitCallback handlerDelegate = delegate(object state) { var handler = this.NotifyArrived; if (handler != null) { handler(data); } }; ThreadPool.QueueUserWorkItem(handlerDelegate); } }}
However, such an isolation design cannot meet the behavior requirements such as "must handle events first and then. Because threadpool only delegates to each waitcallback and assigns a thread for processing, there is no ability to set the execution sequence between each thread.
This problem plagued me for a few days, and then I came up with a solution. The main thread and the event thread are separated by a layer of queue. The waitcallback delegates to be executed are all stored in this queue. In addition, the lock mechanism forces only one thread at a time to process the waitcallback delegation in the queue. With this design, the main thread that executes the operation logic can be isolated from the event thread that issues the event. The sample code is as follows:
namespace ConsoleApplication1{ class Program { static void Main(string[] args) { var obj = new ClassB(); obj.NotifyArrived += new Action<int>(obj_NotifyArrived); obj.Start(); Console.ReadLine(); obj.Stop(); } static void obj_NotifyArrived(int data) { Console.WriteLine(data); } } public class ClassB { // Fields private bool _isRunning = true; private readonly object _eventSyncRoot = new object(); private readonly Queue<WaitCallback> _eventDelegateQueue = new Queue<WaitCallback>(); // Methods public void Start() { Thread thread = new Thread(this.Run); thread.Start(); } public void Stop() { _isRunning = false; } private void Run() { int i = 0; while (_isRunning == true) { Thread.Sleep(100); this.OnNotifyArrived(i++); } } // Events public event System.Action<int> NotifyArrived; private void OnNotifyArrived(int data) { // Queue EventDelegate WaitCallback eventDelegate = delegate(object state) { var handler = this.NotifyArrived; if (handler != null) { handler(data); } }; lock (_eventDelegateQueue) { _eventDelegateQueue.Enqueue(eventDelegate); } // Run EventDelegate WaitCallback handlerDelegate = delegate(object state) { lock (_eventSyncRoot) { WaitCallback runEventDelegate = null; lock (_eventDelegateQueue) { if (_eventDelegateQueue.Count > 0) { runEventDelegate = _eventDelegateQueue.Dequeue(); } } if (runEventDelegate != null) { runEventDelegate(null); } } }; ThreadPool.QueueUserWorkItem(handlerDelegate); } }}
Finally, we need to complete this isolation design. It is also feasible to use an independent thread instead of threadpool to process waitcallback delegates. Only one independent thread is used for processing, and additional work for managing this independent thread needs to be added. This depends on everyone's choice and consideration.