Tuesday, June 27, 2006
I'm not sure how often people run into situations where the ManualResetEvent is needed, but I have a few times. System.Threading.ManualResetEvent provides an easy way to allow cross-thread communication and to let other threads know when something has completed. Most of the time that I've needed it, I have a property in a class that is loaded in another thread, but I want to prevent access to the property until it is loaded. From what I've seen, this is a great time to use the ManualResetEvent.

It is easiest to explain with some code snippets. Here's how you create the ManualResetEvent.

Private _dataLock As New ManualResetEvent(False)

The constructor takes a boolean that signifies whether the object is signaling or not. A signaling object implies that the action has been completed... we're done with the work. If signaling is false, the work hasn't finished yet.

To wait on the signal, use this code:

_dataLock.WaitOne()

You can also specify a period of time to wait instead of waiting indefinitely.

Once your background work is completed, you can call Set like so:

_dataLock.Set()

This begins signaling, so any calls to wait will stop blocking and will continue execution.

Here's a full code sample that shows how it would work and also gives a short example on the BackgroundWorker.

Imports System.ComponentModel
Imports System.Threading

Module Module1

    
Private _dataLock As New ManualResetEvent(False)

    
Sub Main()

        Thread.CurrentThread.Name =
"[Main]"

        Dim wkr As New BackgroundWorker
        
AddHandler wkr.DoWork, AddressOf wkr_DoWork
        
AddHandler wkr.RunWorkerCompleted, AddressOf wkr_RunWorkerCompleted

        wkr.RunWorkerAsync(
"Get to work!")

        Write(
"Waiting on the lock...")
        _dataLock.WaitOne()
        Write(
"we're back!")


        Console.WriteLine(vbNewLine &
"Press any key to continue...")
        Console.ReadLine()
    
End Sub

    Private Sub wkr_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs)
        Thread.CurrentThread.Name =
"[Work]"

        Write("DoWork just received this value: " & CStr(e.Argument))
        Thread.Sleep(
5000)

        e.Result =
"DoWork finished!"

        Write("DoWork is done so start signaling completion.")
        _dataLock.Set()
    
End Sub

    Private Sub wkr_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)
        
Dim wkr As BackgroundWorker = DirectCast(sender, BackgroundWorker)
        
RemoveHandler wkr.DoWork, AddressOf wkr_DoWork
        
RemoveHandler wkr.RunWorkerCompleted, AddressOf wkr_RunWorkerCompleted
        wkr.Dispose()
    
End Sub

    Private Sub Write(ByVal s As String)
        Console.WriteLine(
String.Format("Thread: {0}, Message: {1}", Thread.CurrentThread.Name, s))
    
End Sub

End
Module

The output from running looks like this:
Thread: [Main], Message: Waiting on the lock...
Thread: [Work], Message: DoWork just received this value: Get to work!
Thread: [Work], Message: DoWork is done so start signaling completion.
Thread: [Main], Message: we're back!

Press any key to continue...
One thing to remember: do NOT put the call to Set (i.e. _dataLock.Set()) in the thread where you are waiting (i.e. _dataLock.WaitOne())! You'll never get the signal because that thread is already blocking! Because of this, make sure your call to Set always happens in the DoWork event of the BackgroundWorker instead of the RunWorkerCompleted event.
Comments are closed.