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
top
The BeginInvoke, EndInvoke, and Invoke methods
Except for the return value, the BeginInvoke
and Invoke
methods have the same signature:
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:
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:
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.
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
:
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
{
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:
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:
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:
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:
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
SynchronizationContext
method overrides
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.