< Day Day Up > |
TechniqueThe technique here involves using System.Threading.ManualResetEvent or System.Threading.AutoResetEvent . For now, we consider ManualResetEvent . An event is a thread synchronization object that can be signaled or nonsignaled. The idea is that the thread that needs to suspend execution does so by invoking the method ManualResetEvent.WaitOne() . It allows the thread to proceed only if the event is signaled. If the event is not signaled, then the thread blocks until the event becomes signaled. Obviously, some other running thread needs to set the event to signaled when it detects that the first thread can continue. In terms of code, you first need to instantiate the event. Because this event needs to be accessible to multiple threads, you might want to store it as a field of some class: class ThreadSyncClass { private ManualResetEvent syncEvent = new ManualResetEvent(false); The event constructor takes a Boolean parameter that indicates whether the event should start signaled. The preceding code instantiates an event in the nonsignaled (blocking) state. To make the executing thread block (wait) until the event is signaled, simply call this code: syncEvent.WaitOne(); You can set an event to signaled like this: syncEvent.Set(); Note that there is no limit to the number of threads that can call ManualResetEvent.WaitOne() . When an event is set to signaled, all threads that are waiting on it are unblocked and can resume. Because the ManualResetEvent class wraps a native object, you should be sure to call Dispose() when you finish with it: syncEvent.Dispose(); So far the discussion has concerned the ManualResetEvent class. All the preceding code would work equally well if syncEvent had been defined to be of type AutoResetEvent . The only difference between the classes is that if AutoResetEvent.Set() is called, then the event becomes signaled only very briefly , allowing any waiting threads to continue, but then immediately reverts to nonsignaled. In contrast, ManualResetEvent remains signaled until it is explicitly reset by calling its Reset() method: syncEvent.Reset(); CommentsOne common use for events is for worker threads to let the calling thread know when they finish some task; we indicated earlier that this step can be particularly important when queuing threads to the thread pool. To illustrate the technique in this context, we continue the earlier QueueUserWorkItem() example. Let's suppose that the main thread will queue some task and then carry on with some other work but will at some point reach a point where it can't do anything else until the worker thread finishes its task. Perhaps the main thread set the worker thread off retrieving some data from the network, but now the main thread needs to process that data. One possible solution to this problem using events follows . The main thread executes this code: ManualResetEvent taskFinished = new ManualResetEvent(false); // assume WorkerClass.DoSomeWork() is the method to be queued WorkerClass worker = new WorkerClass(""); WaitCallback task = new WaitCallback(worker.DoSomeWork); ThreadPool.QueueUserWorkItem(task, taskFinished); Console.WriteLine("background task is running"); // do any work here that can be done while the background task is still running taskFinished.WaitOne(); Console.WriteLine("background task is done"); // do any work here that must wait until the background task is completed Notice how the event is passed into the background task as the application-specific state data. The code for the background task, the DoSomeWork() method, looks like this: public void DoSomeWork(object state) { ManualResetEvent finished = (ManualResetEvent)state; // put code to perform the task here finished.Set(); } Notice how in this code the event used to synchronize the threads is passed to the worker thread as the state parameter. In general, events are the simplest of the thread synchronization primitives and are used when you need to explicitly code the logic governing when threads should block. You use other, more sophisticated primitives for more specific scenarios; for example, you use the Monitor class behind the C# lock statement for synchronizing access to variables . Other primitives include Mutex and ReaderWriterLock , both of which you also use for synchronizing access to variables and which we consider next . ManualResetEvent and AutoResetEvent , along with Mutex , are all inherited from a base class, WaitHandle . It is in fact the WaitHandle class that provides the implementation of the WaitOne() method. In general, WaitHandle provides a .NET wrapper around the thread synchronization mechanism supplied by the Windows operating system. |
< Day Day Up > |