.net, C# tip, Threading

How to use ManualResetEvent in C# to block one thread until another has completed

This post is mainly focused on C#, but is also the second of my posts on using the digitalPersona U.are.U 4000B fingerprint sensor.

I left the previous post with my code throwing an exception – the sensor’s SDK is designed so that fingerprint capture is asynchronous. After telling the sensor to start capturing, the main thread isn’t blocked. Instead, when the device completes a scan, the OnComplete event fires on a separate worker thread.

However, I want to be able to enroll a fingerprint on the main thread, and make this thread wait until enrollment has finished on the worker thread before proceeding.

The C# framework provides a way of doing this with the ManualResetEvent class. This allows threads to communicate between each other – usually in the use case of blocking one thread until it receives a signal from another that it’s allowed to proceed. This is perfect to meet my needs in this program.

I find it useful to think of the ManualResetEvent as a logical opposite to the BackgroundWorker class which I’ve blogged about before. I see ManualResetEvent as a way to block an asynchronous process by making it a synchronous one – and the BackgroundWorker class is a way to unblock a synchronous process by making it an asynchronous one.

Even though the ManualResetEvent class has fewer use cases than the BackgroundWorker, I still view it as important to understand, and have it available in your programming toolbox.

It’s pretty simple to use the ManualResetEvent class:

  • Instantiate the ManualResetEvent class;
  • Start the main thread;
  • When an asynchronous worker thread is triggered, call the ManualResetEvent object’s WaitOne() method to block the main thread;
  • When the worker thread has completed, call the ManualResetEvent object’s Set() method to release the main thread and allow it to continue.

I modified my code to use this class, and I’ve pasted this below with the new code highlighted in bold. As you can see, I’ve only added three lines of code.

public class DigitalPersonaFingerPrintScanner : DPFP.Capture.EventHandler, IFingerprintScanner
{
    private ManualResetEvent _mainThread = new ManualResetEvent(false);
    private Capture _capture;
    private Sample _sample;
 
    public void Enroll()
    {
        _capture = new Capture();
        _capture.EventHandler = this;
        _capture.StartCapture();
        _mainThread.WaitOne();
    }
 
    public void CreateBitmapFile(string pathToSaveBitmapTo)
    {
        if (_sample == null)
        {
            throw new NullReferenceException(nameof(_sample));
        }
 
        var sampleConvertor = new SampleConversion();
        Bitmap bitmap = null;
        sampleConvertor.ConvertToPicture(_sample, ref bitmap);
 
        bitmap.Save(pathToSaveBitmapTo);
    }
 
    public void Dispose()
    {
        _capture?.StopCapture();
        _capture?.Dispose();
    }
 
    public void OnComplete(object capture, string readerSerialNumber, Sample sample)
    {
        _capture.StopCapture();
        this._sample = sample;
 
        _mainThread.Set();
    }
 
    public void OnFingerGone(object capture, string readerSerialNumber) { }
    public void OnFingerTouch(object capture, string readerSerialNumber) { }
    public void OnReaderConnect(object capture, string readerSerialNumber) { }
    public void OnReaderDisconnect(object capture, string readerSerialNumber) { }
    public void OnSampleQuality(object capture, string readerSerialNumber, CaptureFeedback captureFeedback) { }
}

Now I’m able to successfully run main method of my program synchronously using the code below, and enroll a fingerprint fully before generating the bitmap.

using (var scanner = new DigitalPersonaFingerPrintScanner())
{
    scanner.Enroll();
    scanner.CreateBitmapFile(@"C:\Users\jeremy\Desktop\fingerprint.bmp");
}