A Fistful of WaitHandles - Part Two

by Scosby Wednesday, February 22, 2012

Introduction

This is the second and final post in the series. The first post talks about the scenario for and behavior of the Job class. This post talks about the technical implementation of the Job class and how to extend it to suit your needs.

I’ve included the scenario from the first post, just in case you haven’t read it yet.

Scenario

If you need to run an operation after a specific amount of time, then it is likely you are familiar with one of the many timers available in the .NET Framework. This is a good approach and is well documented. Furthermore, this approach will continue to be a valuable tool for many developers to use in many different applications.

If you are interested in running the timer’s job on demand, in addition to its interval, then you will need to do a bit more work. Of course, this is still reasonable to do with a Timer but it does provide an opportunity to consider another approach. You will learn about scheduling jobs to the ThreadPool in a way that resembles the familiar timers in the .NET Framework.

Code Samples

The following code sample represents the Job class. As a reminder, this class is designed to run after a certain amount of time passes, additionally, it can be run on demand. The Job class encapsulates the code to do those behaviors.

 Job Class

    using System.Threading;

 

    public class Job : IDisposable

    {

        private AutoResetEvent runWaitHandle = new AutoResetEvent(false);

        private RegisteredWaitHandle registeredWaitHandle;

 

        public Job()

        {

            this.Interval = -1;

        }

 

        public int Interval { get; set; }

 

        public void Start()

        {

            WaitOrTimerCallback callback =

                (userState, interval) =>

                {

                    if (interval)

                    {

                        Console.WriteLine("Operation ran on schedule.");

                    }

                    else

                    {

                        Console.WriteLine("Operation ran on demand.");

                    }

                };

 

            registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(

                runWaitHandle, //A WaitHandle to be used by the thread pool

                callback, //The operation to execute

                null, //User state passed to the operation (not used here)

                this.Interval, //How often to execute the operation

                false); //Run once or not

        }

 

        public void Stop()

        {

            registeredWaitHandle.Unregister(null);

        }

 

        public void Run()

        {

            runWaitHandle.Set();

        }

 

        public void Dispose()

        {

            if (registeredWaitHandle != null)

            {

                registeredWaitHandle.Unregister(null);

            }

 

            if (runWaitHandle != null)

            {

                runWaitHandle.Dispose();

            }

        }

    }

If you remember the first post, we discussed the three steps for scheduling jobs to the ThreadPool. Let’s look at those steps now and see how they are implemented in the Job class.

1.      You need to start, or register, the job.

a.      The Job class exposes a Start method, which queues the operation to the ThreadPool.

b.      If the Interval property is -1 the operation will not run on a schedule.

2.      You provide a special object that helps the ThreadPool know when to run your job.

a.      The ThreadPool.RegisterWaitForSingleObject method uses a WaitHandle to control the operation execution.

b.      When using an AutoResetEvent, not only will the ThreadPool run the operation on a schedule but it is also possible to tell the ThreadPool to run your operation on demand.

c.       Since the AutoResetEvent is a member field that implements IDisposable, our Job class needs to implement the same interface and cleanup the AutoResetEvent.

3.      In order to stop your job, you need to keep a reference to the object returned after you registered the job.

a.      The ThreadPool.RegisterWaitForSingleObject method returns a RegisteredWaitHandle object after you start the job.

b.      The RegisteredWaitHandle can be used to stop the job.

c.       Stopping the operation is easy as calling the RegisteredWaitHandle.Unregister method. This is done when disposing the class too.

As you can see, the Job class is a neat way to wrap up all the behavior described by the scenario. Additionally, it provides a foundation you can build upon for other uses. I will finish the post by describing a few ways you could extend the Job class.

Extending The Job Class

The following ideas could be incorporated into the Job class. These ideas have varying degrees of complexity. Hopefully, you can use these ideas as inspiration to improve the Job class and customize it to your needs.

·         Add reentrancy to the callback operation.

·         Make the callback operation a protected virtual method to enable sub classing specific jobs.

·         Support stopping and then restarting the job.

o   Add a property to the Job class to check if it is registered or not.

·         Add a public property allowing run once configuration when registering to the ThreadPool.

Summary

This series covered a complex scenario: running a job periodically and sometimes on demand. You have seen one way to do this by using the ThreadPool.RegisterWaitForSingleObject method. Furthermore, you have a seen the benefits of abstracting the problem away from the code.

Tags: ,

IT | Programming

blog comments powered by Disqus