Click here to Skip to main content
15,867,704 members
Articles / Programming Languages / C#

A DelegateQueue Class

Rate me:
Please Sign up or sign in to vote.
4.81/5 (44 votes)
13 Mar 2007CPOL14 min read 233.7K   1.8K   219   52
An implementation of the ISynchronizeInvoke interface.

Contents

Introduction

Last year, I wrote a toolkit for creating state machines. One of my goals for the toolkit was to provide the option of having each state machine executing in its own thread; I wanted state machines to be active objects. To realize this goal, I needed an event queue. State machines would use event queues to enqueue events sent to them and later dequeue them from their own thread. After several designs, I settled on creating a DelegateQueue class. This class provides functionality for placing delegates and their arguments in a queue and later de-queueing them from a thread dedicated to invoking them. Each state machine uses a DelegateQueue object to implement asynchronous behavior.

In addition, I thought it would be nice to have the DelegateQueue class implement the ISynchronizeInvoke interface. And with the advent of .NET v2.0, I wanted to have the DelegateQueue class derive itself from the new SynchronizationContext class. Far from being a class that is only useful for my state machine toolkit, it is a class that is useful in its own right. For this reason, I've removed it from my state machine toolkit and placed it in a new namespace I've created called Sanford.Threading.

What follows is a description of the ISynchronizeInvoke interface, and the implementation of my DelegateQueue class. I also cover overriding methods in the SynchronizationContext class.

top

The ISynchronizeInvoke interface

The ISynchronizeInvoke interface represents functionality for invoking a delegate synchronously or asynchronously. Unfortunately, there are few examples of classes in the .NET Framework that implement this interface. Really, you only have the Control class and its derived classes. Most of us are familiar with the prohibition against modifying, or even accessing, a Control from any thread other than the one in which it was created. The ISynchronizeInvoke interface represents a set of methods and properties that can be accessed from any thread. It provides the ability to marshal an operation to the same thread in which the ISynchronizeInvoke object is running. Marshaling an operation ensures that it is carried out in a thread safe way. I'll give an example of this after we examine the members of the ISynchronizeInvoke interface.

Let's look at the ISynchronizeInvoke interface members:

  • Methods
    • BeginInvoke
    • EndInvoke
    • Invoke
  • Properties
    • InvokeRequired

top

The BeginInvoke, EndInvoke, and Invoke methods

Except for the return value, the BeginInvoke and Invoke methods have the same signature:

C#
IAsyncResult BeginInvoke(Delegate method, object[] args);

object Invoke(Delegate method, object[] args);

The first parameter is a delegate representing the method to invoke. The second parameter is an object array representing arguments to pass to the delegate when it is invoked. Both methods have the same purpose in that they execute the delegate passed to them on the thread in which the ISynchronizeInvoke object is running. However, BeginInvoke operates asynchronously, whereas Invoke operates synchronously. When BeginInvoke is called, it returns immediately without waiting for the specified delegate to be invoked. In contrast, Invoke does not return until the specified delegate has been invoked.

The BeginInvoke method returns an IAsyncResult object representing the status of the BeginInvoke operation. Clients can pass the IAsyncResult object to the EndInvoke method to wait until the delegate has been invoked. Both the EndInvoke and Invoke methods return an object representing the return value of the delegate invocation. What this implies is that if the delegate returns a null value or if its return type is void, the return value will be null.

top

The InvokeRequired property

The InvokeRequired property represents a bool value indicating whether you must call either BeginInvoke or Invoke in order to invoke an operation on the ISynchronizeInvoke object. InvokeRequired will be true if it is checked on a thread other than the one in which the ISynchronizeInvoke object is running; otherwise, it will be false.

For example, say that a Windows Form, which is derived from the Control class and thus implements the ISynchronizeInvoke interface, receives an event from an object telling it to update itself or one of its controls. In its event handler, it checks its InvokeRequired property to see if it is true. If so, it will need to pass a delegate, representing the method in which the actual logic for handling the event is located, to either BeginInvoke or Invoke. When the delegate is actually invoked, it will have been marshaled to the same thread in which the Form is running. Otherwise, if the InvokeRequired property is false, the method for handling the event can be called directly:

C#
private void SomeEventHandler(object sender, EventArgs e)
{
    if(InvokeRequired)
    {
        EventHandler handler = new EventHandler(UpdateControl);

        BeginInvoke(handler, e);
    }
    else
    {
        UpdateControl(sender, e);
    }
}

private void UpdateControl(object sender, EventArgs e)
{
    someLabel.Text = "Some event occurred.";
}

top

Begin Invoke - The Dirty Details

I made an interesting observation about how the Windows Form implements the BeginInvoke method. If BeginInvoke is called on the same thread in which the Form is running, the Form doesn't wait about invoking the method; it invokes it synchronously. For example, consider this Form class:

C#
namespace FormTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            BeginInvoke(new MethodInvoker(delegate ()
            {
                MessageBox.Show("Hello, ");
            }));

            MessageBox.Show("World!");
        }
    }
}

The MessageBox displays "Hello, " before the second MessageBox displays "World!" In other words, it appears that if BeginInvoke is called on the same thread in which the Form is running, it invokes the method passed to it synchronously. This makes sense when we think about it. If the code above had called EndInvoke to wait for the invocation to complete and the invocation happened asynchronously, we would have deadlock. The Form's thread would block before getting a chance to invoke the method.

If we change the code to check the IAsyncResult object to see if the operation completed synchronously, the result is false. Quite frankly, this has me confused.

C#
IAsyncResult result = BeginInvoke(new MethodInvoker(delegate ()
{
    MessageBox.Show("Hello, ");
}));            

MessageBox.Show("World!");

EndInvoke(result);

MessageBox.Show(result.CompletedSynchronously.ToString());

The third message box displays false.

I don't want to get sidetracked on this issue, but I don't understand why the CompletedSynchronously property isn't true. It doesn't make sense.

top

Implementing the ISynchronizeInvoke interface

Implementing the ISynchronizeInvoke interface was straightforward, but there were a few gray areas, which I will describe. I will also describe the overall design of the DelegateQueue class, and go one-by-one through the methods and properties of the ISynchronizeInvoke interface, and talk about how the DelegateQueue class implements each of them.

The DelegateQueue class runs in a single thread throughout its lifetime. Each time BeginInvoke or Invoke is called, the specified delegate and its arguments are placed on a queue. The thread in which the DelegateQueue is running is signaled, and it dequeues the delegate at the front of the queue and invokes it. In this way, the DelegateQueue class uses an unbounded buffer architecture. The process of dequeuing delegates and invoking them continues until the DelegateQueue is disposed of. When that happens, the thread finishes.

Since the DelegateQueue uses an unbounded buffer, there is the danger of an overflow occurring. This shouldn't be a problem as long as the DelegateQueue can dequeue and invoke its delegates roughly as fast as they are being enqueued. Just something to keep in mind.

top

Implementing BeginInvoke, EndInvoke, and Invoke

When the BeginInvoke method is called, it creates a DelegateQueueAsyncResult object. The DelegateQueueAsyncResult class is a private class that implements the IAsyncResult interface and provides additional functionality used by the DelegateQueue class. The object is passed the specified delegate and its arguments when it is created. It is then placed on a queue, and the thread is signaled that it has a delegate to invoke. Finally, the BeginInvoke method returns the DelegateQueueAsyncResult object to the client. Because the return type for the BeginInvoke method is an IAsyncResult object, clients only see the methods and properties exposed by the IAsyncResult interface. If clients wish to wait for the result of the delegate invocation, they can call EndInvoke, passing it the IAsyncResult object. EndInvoke will block until the delegate has been invoked and returns.

The IAsyncResult interface has an AsyncWaitHandle property of the WaitHandle type. When the IAsyncResult object is passed to the EndInvoke method, the method uses this property to wait for a signal from the DelegateQueue's thread that it has invoked the delegate. The DelegateQueueAsyncResult class uses a ManualResetEvent object rather than an AutoResetEvent object, for implementing the AsyncWaitHandle property. The reason is that it is possible that the delegate will be invoked and the event signaled before a call is made to EndInvoke. Using ManualResetEvent instead of AutoResetEvent ensures that the event will remain signaled and not reset itself before it can be checked with a call to EndInvoke.

In light of the observations I described above regarding how BeginInvoke behaves in a Windows Form, I've changed the DelegateQueue to behave the same way. When BeginInvoke is called from the same thread in which the DelegateQueue is running, it invokes the specified delegate immediately and does not enqueue it.

Implementing Invoke was simply a matter of enqueueing the delegate and its arguments and returning the results of EndInvoke:

C#
public object Invoke(Delegate method, object[] args)
{
    if(InvokeRequired)
    {
        DelegateQueueAsyncResult result = new DelegateQueueAsyncResult(this, 
                method, args, false, NotificationType.None);

        lock(lockObject)
        {
            delegateDeque.PushBack(result);

            Monitor.Pulse(lockObject);
        }

        returnValue = EndInvoke(result);
    }
    else
    {
        // Invoke the method here rather than placing it in the queue.
        returnValue = method.DynamicInvoke(args);
    }

    return returnValue;
}

Invoke will block until EndInvoke returns, thus achieving synchronous behavior. If Invoke is called on the same thread in which the DelegateQueue is running, it immediately invokes the specified delegate.

In addition to the Invoke and BeginInvoke methods, the DelegateQueue class provides two variations on these methods not found in the ISynchronizeInvoke interface, called InvokePriority and BeginInvokePriority. Both methods behave as their counterparts except that they allow you to place delegates at the front of the queue rather than at the back. This gives the delegates' invocations priority over those of delegates already in the queue.

After a delegate is invoked as a result of a call to BeginInvoke or BeginInvokePriority, the DelegateQueue raises an event called InvokeCompleted. The EventArgs derived object accompanying this event is called InvokeCompletedEventArgs. It contains information about the invocation, such as the method that was invoked, its arguments, and the return value of the delegate. In addition, if an exception was thrown from the delegate when it was invoked, the Error property represents that exception.

The InvokeCompleted event is also not part of the ISynchronizeInvoke interface. However, I thought it would be helpful to receive event notification after a delegate is invoked, along with information about the invocation. This can be especially helpful if an exception occurred.

top

Implementing InvokeRequired

The InvokeRequired property is implemented by comparing the DelegateQueue's worker thread ID with the thread ID of the current thread, the thread in which the InvokeRequired property is being checked. The code for the InvokeRequired property is straightforward:

C#
public bool InvokeRequired
{
    get
    {
        return Thread.CurrentThread.ManagedThreadId != 
                                        delegateThread.ManagedThreadId;
    }
}

top

Handling exceptions

What do you do when an exception is thrown from the delegate being invoked? The ISynchronizeInvoke's documentation for the Invoke method states the following:

Exceptions raised during the call are propagated back to the caller.

Fair enough. The DelegateQueue implements this by catching any exceptions thrown from the invoked delegate, and re-throws it from the Invoke method (actually, it is re-thrown from the EndInvoke method called inside the Invoke method). However, what happens when BeginInvoke is called instead of Invoke? Here, the documentation is not so clear. Through testing a Windows Form, I discovered that exceptions are re-thrown from the EndInvoke method. However, there is no guarantee that clients will call the EndInvoke method. So, the Windows Form class also re-throws the exception from where it occurred. The application treats it as an unhandled exception.

Re-throwing the exception from where it occurred is not an option for the DelegateQueue class. This would terminate the DelegateQueue's thread; that's not what we want. Instead, when a delegate is invoked as a result of a call to BeginInvoke or BeginInvokePriority and an exception is thrown, the exception is caught and re-thrown from EndInvoke, just as it is with the Windows Control class. In addition, the exception is passed along through the InvokeCompleted event.

top

Deriving from the SynchronizationContext class

The SynchronizationContext class is a new .NET Framework class representing, well, a synchronization context. It is similar in purpose to the ISynchronizeInvoke interface in that it represents a way to marshal delegate invocations from one thread to another. The SynchronizationContext class provides an advantage over the ISynchronizeInvoke interface in that it has a static Current property that gives you access to the SynchronizationContext object for the current thread. This makes it easier to move the responsibility for marshaling events from the receiver to the sender.

I thought it would be useful to derive the DelegateQueue class from the SynchronizationContext class. Specifically, I wanted to override the SynchronizationContext class' Post and Send methods. I also wanted to have each DelegateQueue object set itself as the SynchronizationContext object for the thread it represents. All of this was easy to do. Here are the implementations for the Send and Post methods:

C#
public override void Send(SendOrPostCallback d, object state)
{
    Invoke(d, state);
}

The Send method represents functionality for sending a message to a SynchronizationContext synchronously. To implement this with the DelegateQueue class, I simply delegate the call to Send to the Invoke method.

Originally, I had the Post method simply delegate the call to the BeginInvoke method. However, because I've changed the behavior of BeginInvoke as described above, I have updated the Post's implementation:

C#
public override void Post(SendOrPostCallback d, object state)
{
    lock(lockObject)
    {
        delegateDeque.PushBack(new DelegateQueueAsyncResult(this, d, 
             new object[] { state }, false, NotificationType.PostCompleted));

        Monitor.Pulse(lockObject);
    }
}

The Post method invokes the specified method asynchronously regardless of whether it is called on the same thread in which the DelegateQueue is running. There is no EndInvoke counterpart for the Post method, so there's no danger of deadlock. And I specifically wanted every call to Post to behave asynchronously for use with my state machine toolkit.

There is a concept called Run to Completion (RTC). Applied to state machines, what it means is that each transition must complete before another is triggered. If this is not enforced, a state machine can be left in an undefined state. I won't go into the dirty details here, but the bottom line is that if my Post method behaved like BeginInvoke, RTC would be violated when state machines send messages to themselves (using a DelegateQueue's Post method).

The DelegateQueue sets itself as the SynchronizationContext for its thread, inside its thread method, with one line of code:

C#
// Set this DelegateQueue as the SynchronizationContext for this thread.
SynchronizationContext.SetSynchronizationContext(this);

This enables access to the DelegateQueue via the SynchronizationContext's static Current property so long as the property is accessed from some where on the DelegateQueue's thread.

Like the BeginInvoke and BeginInvokePriority methods, an event is raised after a delegate has been invoked as a result of a call to the Post method. The event, PostCompleted, carries with it an EventArgs derived class called PostCompletedEventArgs. This class represents information about the event such as the callback method that was invoked, the state object that was passed along with the callback, and an Error property representing a possible exception that was thrown when the callback was invoked.

top

DelegateQueue class overview

Now that we've gone over how parts of the DelegateQueue class are implemented, I thought it might be useful to list the methods and properties that belong the DelegateQueue class.

  • ISynchronizeInvoke methods
    • BeginInvoke
    • EndInvoke
    • Invoke
  • ISynchronizeInvoke properties
    • InvokeRequired
  • SynchronizationContext method overrides
    • Post
    • Send
  • DelegateQueue methods
    • InvokePriority
    • BeginInvokePriority
    • SendPriority
    • PostPriority
  • DelegateQueue events
    • InvokeCompleted
    • PostCompleted

In addition, the DelegateQueue class implements the IComponent and IDisposable interfaces.

top

Dependencies

The demo project download for this article includes the entire Sanford.Threading namespace. This includes my DelegateScheduler class. This namespace is fairly small, and both the DelegateQueue and the DelegateScheduler use some of the same classes, so I decided to leave them together. You should know, however, that the Sanford.Threading namespace depends on one of my other namespaces, Sanford.Collections. In the latest update, I've included a copy of the release version of the Sanford.Collections assembly. The projects in the solution that need the assembly are linked to it. I've done this in hopes that the download will compile "out of the box." This has been a source of frustration in the past, and I hope I've finally found a solution that works. If, however, you find that you need any of my assemblies, you can go here.

top

Conclusion

I hope you've found this article interesting and useful. It was an interesting experience writing this class, and rewarding as well. Comments and suggestions are, as always, welcome. Take care.

top

History

  • 26th October, 2005 - First version.
  • 3rd January, 2006 - Switched to an unbounded buffer.
  • 21st June, 2006 - Derived class from SynchronizationContext, and added the InvokeCompleted event. Major update to article.
  • 17th October, 2006 - Updated article and code.
  • 12th March, 2007 - Updated article and download.

License

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


Written By
United States United States
Aside from dabbling in BASIC on his old Atari 1040ST years ago, Leslie's programming experience didn't really begin until he discovered the Internet in the late 90s. There he found a treasure trove of information about two of his favorite interests: MIDI and sound synthesis.

After spending a good deal of time calculating formulas he found on the Internet for creating new sounds by hand, he decided that an easier way would be to program the computer to do the work for him. This led him to learn C. He discovered that beyond using programming as a tool for synthesizing sound, he loved programming in and of itself.

Eventually he taught himself C++ and C#, and along the way he immersed himself in the ideas of object oriented programming. Like many of us, he gotten bitten by the design patterns bug and a copy of GOF is never far from his hands.

Now his primary interest is in creating a complete MIDI toolkit using the C# language. He hopes to create something that will become an indispensable tool for those wanting to write MIDI applications for the .NET framework.

Besides programming, his other interests are photography and playing his Les Paul guitars.

Comments and Discussions

 
AnswerRe: Licensing? Pin
Leslie Sanford17-Dec-08 12:26
Leslie Sanford17-Dec-08 12:26 
Generalolder IDE version Pin
raveneyes10-Mar-08 22:30
raveneyes10-Mar-08 22:30 
GeneralReally great Pin
chaiguy133719-Sep-07 14:13
chaiguy133719-Sep-07 14:13 
GeneralRe: Really great Pin
Leslie Sanford21-Sep-07 20:44
Leslie Sanford21-Sep-07 20:44 
GeneralReal World Example of Implemenation Pin
chessnovice21-Mar-07 6:34
chessnovice21-Mar-07 6:34 
GeneralRe: Real World Example of Implemenation Pin
Leslie Sanford21-Mar-07 7:43
Leslie Sanford21-Mar-07 7:43 
QuestionProblem Pin
NVMehta21-Mar-07 5:47
NVMehta21-Mar-07 5:47 
AnswerRe: Problem Pin
Leslie Sanford21-Mar-07 5:58
Leslie Sanford21-Mar-07 5:58 
GeneralCompletedSynchronously Pin
Steve Hansen13-Mar-07 5:46
Steve Hansen13-Mar-07 5:46 
GeneralRe: CompletedSynchronously Pin
Leslie Sanford13-Mar-07 6:26
Leslie Sanford13-Mar-07 6:26 
GeneralRe: CompletedSynchronously Pin
Steve Hansen13-Mar-07 6:48
Steve Hansen13-Mar-07 6:48 
GeneralRe: CompletedSynchronously Pin
chaiguy133718-Sep-07 11:06
chaiguy133718-Sep-07 11:06 
AnswerRe: CompletedSynchronously Pin
Philipp Seith27-Nov-07 1:02
Philipp Seith27-Nov-07 1:02 
QuestionC++ and not C# available? Pin
HumanTargetJoe5-Sep-06 9:01
HumanTargetJoe5-Sep-06 9:01 
AnswerRe: C++ and not C# available? Pin
Leslie Sanford5-Sep-06 12:54
Leslie Sanford5-Sep-06 12:54 
GeneralState Machine Toolkit & SendPriority() Pin
yesterdazed25-Aug-06 15:23
yesterdazed25-Aug-06 15:23 
GeneralRe: State Machine Toolkit & SendPriority() Pin
Leslie Sanford25-Aug-06 16:42
Leslie Sanford25-Aug-06 16:42 
GeneralBeginInvoke Complication Pin
Leslie Sanford31-Jul-06 8:49
Leslie Sanford31-Jul-06 8:49 
QuestionImplimentation? Pin
mike99999925-May-06 8:48
mike99999925-May-06 8:48 
AnswerRe: Implimentation? Pin
Leslie Sanford25-May-06 10:06
Leslie Sanford25-May-06 10:06 
GeneralRe: Implimentation? Pin
mike99999925-May-06 11:29
mike99999925-May-06 11:29 
GeneralRe: Implimentation? Pin
Leslie Sanford25-May-06 11:43
Leslie Sanford25-May-06 11:43 
GeneralRe: Implimentation? Pin
mike99999926-May-06 4:33
mike99999926-May-06 4:33 
GeneralThank you, thank you! Pin
Keith Rimington31-Jul-06 3:56
Keith Rimington31-Jul-06 3:56 
GeneralRe: Implimentation? Pin
scott987uk21-Jan-09 12:08
scott987uk21-Jan-09 12:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.