Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

A Better Thread Synchronization Class

4.95/5 (5 votes)
23 Apr 2016CPOL3 min read 16.2K   212  
Wait, timeout and evaluate early thread release criteria easily and clearly with WaitTimeout

Image 1

Introduction

I have always disliked AutoResetEvent and ManuaResetEvent. They are what Microsoft offers us in place of a while loop, Thread.Sleep(n) and some early timeout criteria. But those two classes make your code ugly and complicated, and don't really offer the functionality and clarity that I have always expected.

For instance, the only way to release a waiting thread early is to make sure the worker thread has access to the AutoResetEvent object, and to call .Set(). This means that it must either be passed to the running thread, or you must make sure it is in a high enough scope that it is visible to the thread - maybe by making it a private instance variable.

Also, to evaluate early release of a waiting thread because the worker thread is taking longer than you are willing to allow, you need to start yet ANOTHER thread to monitor the state of the worker thread and pass it the AutoResetEvent object so that it can call .Set() and release your waiting thread for you.

It seems to me that this should be trivial by now. Simple.

With WaitTimeout, we're getting closer.

Using the Code

WaitTimeout is not a replacement for AutoResetEvent. It instead provides a different (and clearer) way of visualizing your code flow in certain use cases using a thread synchronization event class.

AutoResetEvent's Set() and Reset() functions don't describe what I'm doing in my application, they describe what's happening internally in that class.

I'm not interested in that.

I'm writing my own application or library, and when I look at my code, I don't want to see .Set() or Reset(). I want to know if a thread is waiting or being released. I want to be able to see at a glance how long a thread will wait, and what the early timeout criteria is without having to dig through my code to find the monitor thread I have running to handle that. If I'm waiting on multiple threads to complete, I want to know how many I'm waiting on, and I don't necessarily want to have to create an array of AutoResetEvent objects to handle it.

It should be simpler, easier and clearer than that.

WaitTimeout is designed to handle three specific scenarios:

  1. Simple wait / release situations with a timeout (AutoResetEvent like functionality):
    C#
    private WaitTimeout SimpleWaitTimeout;
    private int timeoutDuration;
    
    // Simple Wait() with timeout example: //////////////////////////////////
    private void button1_Click(object sender, EventArgs e)
    {
        try
        {   // Get the timeout duration you specified:
            timeoutDuration = int.Parse(tbSimpleWaitSeconds.Text.Trim());
        }
        catch (Exception)
        {
            MessageBox.Show("The value you type into the 'Wait Seconds' textbox 
            must be numerical.", "Typo?", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
    
        yourComplicatedReleaseEarlyCriteria = false;
        SimpleWaitTimeout                   = new WaitTimeout(timeoutDuration);
    
        // Start the work thread:
        new Thread(delegate()
        {
            // Do some work here...
            SimpleWaitTimeout.Wait();
    
            // Update the UI when we're finished waiting:
            this.Invoke((MethodInvoker)delegate()
            {
                this.lblStatus.Text = "We're out.";
            });
    
        }).Start();
    
        // Updating the UI with the remaining time until timeout from a background thread:
        new Thread(delegate()
        {
            while (!SimpleWaitTimeout.IsTimedOut())
            {
                this.Invoke((MethodInvoker)delegate()
                {
                    this.lblStatus.Text = "Remaining time: " + 
                    SimpleWaitTimeout.GetRemainingSeconds() + " seconds.";
                });
    
                Thread.Sleep(1);
            }
    
        }).Start();
    }
    
    private void button2_Click(object sender, EventArgs e)
    {
        // Simply call .release() when you wish to stop Waiting, for
        // AutoResetEvent .Set() type functionality.
        SimpleWaitTimeout.Release();
    }
  2. Wait / release scenarios where you want to have early release criteria evaluated without the hassle and clutter, like so:
    C#
    // Wait() with timeout and end user code evaluated for early release example:
    // The idea here is that you have a thread waiting on one or more threads to complete. 
    // You construct your WaitTimeout specifying timeoutDuration as the 
    // maximum amount of milliseconds you will allow this thread to wait,
    // while having WaitTimeout evaluate the criteria in your anonymous delegate 
    // to determine if it should release early. For our example, 
    // the anonymous delegate is only checking to see if the yourComplicatedReleaseEarlyCriteria
    // bool has been set to true by you, after clicking the release early button.
    private void btWaitTimeoutWithDelegate_Click(object sender, EventArgs e)
    {
        try
        {   // Get the timeout duration you specified:
            timeoutDuration = int.Parse(tbWaitSecondsWithDelegate.Text.Trim());
        }
        catch (Exception)
        {
            MessageBox.Show("The value you type into the 'Wait Seconds' 
            textbox must be numerical.", "Typo?", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
    
        // You can add any early release evaluation code here, but it must return a bool
        // indicating the early release status. true = release now and false = continue to wait.
        yourComplicatedReleaseEarlyCriteria = false;
        WaitTimeout waitTimeout             = new WaitTimeout(timeoutDuration, delegate() 
                                              { return yourComplicatedReleaseEarlyCriteria; });
    
        new Thread(delegate()
        {
            // Do some work here...
            waitTimeout.Wait();
    
            this.Invoke((MethodInvoker)delegate()
            {
                this.lblStatus.Text = "We're out.";
            });
    
        }).Start();
    
        // Using .IsTimmedOut() and .GetRemainingSeconds() to display the remaining time:
        new Thread(delegate()
        {
            while (!waitTimeout.IsTimedOut())
            {
                this.Invoke((MethodInvoker)delegate()
                {
                    this.lblStatus.Text = "Remaining time: " + 
                                           waitTimeout.GetRemainingSeconds() + " seconds.";
                });
    
                Thread.Sleep(1);
            }
    
        }).Start();
    }
    
    private void btWaitTimeoutWithDelegateExitEarly_Click(object sender, EventArgs e)
    {
        yourComplicatedReleaseEarlyCriteria = true;
    }
  3. And scenarios where you have started multiple worker threads, and you need to wait for them to complete - but you want to stop waiting for them if they take too long, when they all report in, or if some early release criteria is met (for instance, if your end user is tearing his hair out in frustration and mashing the cancel button):
    C#
    // WaitOn() with timeout and end user code evaluated for early release 
    // while waiting on 15 threads to complete.
    // Use this when you have started multiple threads, and you need to wait for them 
    // all to finish before execution can continue
    // on this thread. The value you set in timeoutDuration is the 
    // maximum amount of time you are willing to allow before 
    // continuing execution on this thread, weather your worker threads complete or not. 
    // WaitOn() returns true if all threads
    // expected to report in have done so before WaitTimeout has timed out or 
    // been released early, and false otherwise.
    // I know Microsoft has provided functionality that accomplishes 
    // this in the Task Parallel Library, but I find 
    // it to be overly complicated, cumbersome and ugly. This class makes it trivial and clear.
    private void btStartMultipleThreadEval_Click(object sender, EventArgs e)
    {
        try
        {
            timeoutDuration = int.Parse(tbWaitTimeoutMultipleThreads.Text.Trim());
        }
        catch (Exception)
        {
            MessageBox.Show("The value you type into the 'Wait Seconds' 
            textbox must be numerical.", "Typo?", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
    
        // You can add any early release evaluation code here, but it must return a bool
        // indicating the early release status. true = release immediately and 
        // false = continue to wait.
        yourComplicatedReleaseEarlyCriteria = false;
        WaitTimeout waitTimeout             = 
        new WaitTimeout(timeoutDuration, delegate() { return yourComplicatedReleaseEarlyCriteria; });
        bool succeeded                      = false;
        String msg                          = "";
    
        new Thread(delegate()
        {
            this.Invoke((MethodInvoker)delegate()
            {
                this.lblStatus.Text = "Threads completed: 0";
            });
    
            // Start up 15 threads, and have them report in as they complete:
            for (int threads = 1; threads < 16; threads++)
            {
                // Only create new threads if we haven't been released early already:
                if (!waitTimeout.IsTimedOut())
                {
                    new Thread(delegate(Object o)
                                        {
                                            // We box this and unbox it here to 
                                            // make a local copy for our anonymous delegate.
                                            int thisThreadNumber = (int)o;
    
                                            // We need to simulate some work being done, so
                                            // we'll stagger thread completion over 7500 ms
                                            // and bail if we are released early by you, our end user.
                                            // This is a great place to use WaitTimeout. So:
                                            new WaitTimeout(500 * thisThreadNumber, delegate() 
                                            { return waitTimeout.IsTimedOut(); }).Wait();
                                            // What's happening there? We're waiting (blocking) 
                                            // for (500 * thisThreadNumber) milliseconds, 
                                            // and specifying that
                                            // if waitTimeout.IsTimedOut() we should release early and 
                                            // continue with execution on this thread,
                                            // All in a single line of code.
    
                                            // Only report in if we haven't been 
                                            // released early already:
                                            if (!waitTimeout.IsTimedOut())
                                            {
                                                // Report in when complete.
                                                waitTimeout.ReportThreadComplete();
    
                                                // Update the UI:
                                                this.Invoke((MethodInvoker)delegate()
                                                {
                                                    this.lblStatus.Text = "Threads completed: " + 
                                                    waitTimeout.GetNumReportedComplete().ToString();
                                                });
                                            }
    
                                        }).Start(threads);
                }
            }
                    
            // We're waiting on 15 threads to complete. 
            // If they don't complete within the time you specified,
            // We will time out and move on. If we time out 
            // before all 15 have completed, WaitOn() will return false.
            succeeded = waitTimeout.WaitOn(15);
    
            msg = "We're out. Threads completed:" + 
                   waitTimeout.GetNumReportedComplete().ToString();
            msg += succeeded ? " (Success! All threads complete before timeout.)." : " 
            (Failure! Timed out or released early before all threads completed.)";
                    
            this.Invoke((MethodInvoker)delegate() { this.lblStatus.Text = msg; });
    
        }).Start();
    }
    
    private void btMultipleThreadEvalExitEarly_Click(object sender, EventArgs e)
    {
        yourComplicatedReleaseEarlyCriteria = true;
    }

Points of Interest

Yes, I know that Microsoft's TPL and Parallel Foreach can also be used in our "multiple threads reporting" in scenario, but I think if you look at the complication involved, you will agree that this implementation is clearer and simpler. It makes this kind of requirement trivial.

History

  • 23rd April, 2016: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)