Introduction
What is ContextDelegate?
ContextDelegate
is an abstraction that simplifies the delivery of callbacks into any event driven, single-threaded context. A context delegate can be described as a delegate with a built-in affinity to the “context” in which it is created. When such a delegate is invoked, the target method is invoked asynchronously in the original context in which the delegate was created. When the callback-recipient context is a UI context (e.g. WinForms, WebForms), the callback is dispatched on a UI thread within event boundaries. When the callback-recipient context has no special requirements, the callback is dispatched on any available thread (possibly the calling thread).
Why is ContextDelegate Needed?
Fundamentally, a client application is an event processing engine. Application code is typically comprised of visual and non-visual objects that collectively execute in an event driven single-threaded context. In more complex client applications, the roles of the visual and non-visual objects are formalized using a Model-View-Controller design pattern.
At any moment, only one event is being processed by the client application. Most Frameworks deliver system level asynchronous activity (e.g. key press, mouse click or move events) as simple events that are serially processed by the client application. No user interface object ever needs to allow for the possibility of multiple system events racing with each other on multiple threads.
With the advent of multi-threaded client applications, it becomes necessary for the application to deal with non-system asynchronous events (e.g. arrival of data from a server, completion of a long calculation). Historically, it has been left to the application developer to arrange for these events to be safely processed by the same context (i.e. thread) that processes system events. Most resulting ad-hoc solutions involved writing a complex and error-prone mix of thread-safe and single-threaded logic. This in turn, ultimately destroyed the original simplicity and predictability of the event-driven, single-threaded UI model.
ContextDelegate
provides a transparent way to deliver any application generated asynchronous event with the same contract as system level events. This allows these events to be handled with the same simplicity as system level events (e.g. keyboard clicks).
What is a Practical Example?
A typical pattern for publish/subscribe client applications involves a set of one or more threads that subscribe to published updates from a publisher and invoke callbacks for processing of those updates. The callbacks must be executed on the client (i.e. UI) thread. The callbacks must be delivered in a manner that maintains responsiveness of the user interface, even when the frequency of updates is high. ContextDelegate
handles the necessary plumbing to make this possible with a minimum of effort by the application developer.
ContextDelegate Usage
While there are several types involved in the facility, only the ContextDelegate
class is used by the client. The multithreaded service is unaware that it is dealing with a context-sensitive callback. A typical usage idiom involves the following events.
- The client code creates a “regular” .NET delegate.
- The client code calls the
static ContextDelegate.Create()
method to create a context-sensitive delegate from the regular .NET delegate that is passed as the only parameter. A Delegate
type is returned from the Create()
method. The returned delegate refers to the original delegate and implies a strong reference to the target object. - The client code “passes” the returned “context-sensitive” delegate returned from the
Create()
method to a multithreaded service. - The multithreaded service invokes the “context-sensitive” delegate.
- The context-sensitive delegate invokes the original delegate in the context specified by the callback creator.
If the target method for the delegate is decorated with the [ThreadNeutral]
attribute, the ContextDelegate.Create()
method does not perform any special marshalling. This makes it possible for the ContextDelegate
facility to transparently support context-sensitive and context-insensitive observers.
ContextDelegate Implementation
There are classes, interfaces, structs, and attributes involved in the ContextDelegate
facility.
Name | Type | Purpose |
AtomicCount | struct | Multithread-safe counter used by the callback scheduler to maintain statistics about the number of pending callbacks for a specific context. |
Context | class | The Context for a callback consists of a scheduler, a queue of pending callbacks, and a map of delegate facades. |
ContextDelegate | class | The public class used by the client code to create a context-sensitive delegate. |
DelegateFactory | class | Default Factory which recreates a .NET delegate with no facade. |
Facade | class | Provides a facade for a .NET delegate that supports context-sensitive dispatch. |
Factory | class | Used to recreate context delegates. Advanced functionality not used by typical application code. |
IDelegateFactory | interface | Interface used to recreate a context delegate from the underlying method and target object. Advanced functionality not used by typical application code. |
IDelegateProxy | interface | Interface used to access the underlying method or target object. Advanced functionality not used by typical application code. |
IScheduler | interface | The interface implemented by context-sensitive delegate schedulers. |
MessageWnd | class | A non-display window used to schedule callbacks by the STAScheduler. |
STAScheduler | class | A scheduler for standard apartment model threads (i.e. WinForms client code). This scheduler ensures that callbacks are delivered in a manner that ensures user interface responsiveness. |
WebScheduler | class | A scheduler for ASP.NET client applications. |
ThreadNeutral | attribute | Methods with this attribute will have context-sensitive delegates invoked on any available thread including the calling thread. |
User32 | class | Singleton class that exposes selected Win32 API calls used in message dispatch. |
WeakMap | class | A map that maintains only weak references to keys and values. WeakMap is used by the Context class to hold facades. |
The ContextDelegate
facility currently supports .NET delegates with the following parameter signatures:
(object sender, EventArgs args)
(IAsyncResult result)
(object returnValue)
()
The Facade
class can be extended to support other signatures. The facility currently supports a scheduler for WinForms and ASP.NET applications.
The classes in the ContextDelegate
facility interact in the following manner.
ContextDelegate
Event | |
1 | Creates a Context with an associated scheduler and dispatch queue. |
2 | Calls the CreateDelegate method of Context to create a Facade for the requested delegate signature. |
Context
Event | |
3 | Creates a Facade for the requested delegate signature. |
5 | Queues pending callbacks for the associated scheduler. Calls the Schedule method of the associated scheduler to schedule the dispatch of the callback. |
Facade
Event | |
4 | Calls the Post method of Context to schedule a callback. |
STAScheduler
Event | |
6 | Calls the Post method of the MessageWnd to post a WM_USER message. |
7 | Supplies the message procedure to handle the message posted to the MessageWnd . This message procedure is called in the context (i.e. thread) of the client that created the scheduler. |
8 | Calls the Dispatch method of Context to actually invoke the callback. |
Attachments
Visual Studio 2005 projects for the ContextDelegate
implementation and a sample WinForms client application are provided in ZIP file attachments. Use the CtxDelegateSample.sln to build and execute ContextDelegate
and the sample.
The ContextDelegate
implementation runs in production with high update rate multithreaded services. Nevertheless, there are some aspects of the implementation that can be enhanced or further generalized. These are appropriately marked in the source code.
The sample application displays updates from a background subscriber thread on a listbox. The background thread is unaware that it is calling a method that requires UI context. The delegate used by the background thread is passed, along with update rate and update count. The callback from the thread procedure is marshaled to the UI thread by ContextDelegate
. The updates are displayed in the UI thread and the user interface remains completely responsive even with high update rates.
Future Articles
In future articles, more advanced usage of ContextDelegate
by infrastructure services will be explored. An ASP.NET sample application will be provided. Solutions for high update rate environments (e.g. real-time market data), within an MVC pattern, will be described.