Monday, May 2, 2011

WPF: DispatcherOperation - Anatomy

In my last post, we saw how the DispatcherOperation class is used in the underpinnings of Dispatcher.Invoke(). The DispatcherOperation concept is important for all invocation scenarios as well in processing DispatcherHooks events, so it is worth a deeper look.

Static Elements

The static elements of DispatcherOperation are used to ensure that all operations occur within an appropriate security context:

private static ContextCallback _invokeInSecurityContext;

[SecurityCritical, SecurityTreatAsSafe]
static DispatcherOperation()
{
    DispatcherOperation._invokeInSecurityContext = new ContextCallback(DispatcherOperation.InvokeInSecurityContext);
}


[SecurityCritical]
private static void InvokeInSecurityContext(object state)
{
    DispatcherOperation dispatcherOperation = (DispatcherOperation)state;
    dispatcherOperation.InvokeImpl();
}

The point of this code -- as suggested by the member names -- is to make sure that all dispatcher operations happen in a fully-trusted context. If the [SecurityCritical] demand cannot be satisfied, the invocation will fail. In a moment, we'll see where the InvokeInSecurityContext method comes into play, but suffice it to say that all dispatcher activity goes through this code path.

More information is available at the following MSDN topics:

Construction and State

DispatcherOperation objects can only be created from inside the BCL since both of its constructors are declared as internal:

[SecurityCritical]
internal DispatcherOperation(Dispatcher dispatcher, Delegate method, DispatcherPriority priority, object args, int numArgs)
{
    this._dispatcher = dispatcher;
    this._method = method;
    this._priority = priority;
    this._numArgs = numArgs;
    this._args = args;
    this._executionContext = ExecutionContext.Capture();
}

internal DispatcherOperation(Dispatcher dispatcher, Delegate method, DispatcherPriority priority)
{
    this._dispatcher = dispatcher;
    this._method = method;
    this._priority = priority;
    this._status = DispatcherOperationStatus.Aborted;
}


From the signatures of these constructors, we can glean the defining elements of an operation:
  • _dispatcher: the Dispatcher on which the operation will run.
  • _method: the method that will be executed.
  • _priority: the DispatcherPriority for the operation; determines placement in Dispatcher's priority queue.
  • _numArgs: the number of arguments passed to the operation's method.
  • _args: the arguments to pass to the operation's method.
  • _executionContext: the ExecutionContext in which the operation should be invoked.
  • _status: the current status of the operation.

It is interesting to note the differences between these two constructors. The first one lets the status default to Pending, while the second one sets the initial state to Aborted. I hope to uncover under what conditions the second constructor is used and why Aborted would be a natural starting state. This second constructor also does not capture the ExecutionContext for the operation, so it makes me wonder whether any DispatcherOperation initialized through it could ever possibly run.

If you have a reference to a DispatcherOperation, you can change its urgency through its Priority property and inspect its current execution state through its Status property. You can also inspect its Dispatcher if necessary. All other pieces of state mentioned above are immutable and cannot be inspected, which makes working with DispatcherOperation objects a bit frustrating! If you want to see what method an operation refers to or what arguments were passed to it, you have to use reflection to get to the private members. One helpful internal member, which often suits for any logging output you may want to generate for a DispatcherOperation, is the Name property. This property munges together the _method's DeclaringType and the method's name to produce a useful, human-readable descriptor for the operation.

There are only two other pieces of state that need mentioning:
  • _result: the return value from the operation.
  • _item: the PriorityItem<DispatcherOperation> that represents the operation in the Dispatcher's work queue.

The _result field is self-explanatory, and can be retrieved from the DispatcherOperation -- once it completes -- through its Result property. The _item field is used internally along with the PriorityChain<T> and PriorityQueue<T> types to manage the Dispatcher's work queue.

Invocation

As it is with construction, DispatcherOperation invocation can only happen from inside the BCL. The Invoke() instance method is called on the appropriate thread when it is time for the operation to execute:

[SecurityCritical]
internal object Invoke()
{
    this._status = DispatcherOperationStatus.Executing;
    if (this._executionContext !null)
    {
        ExecutionContext.Run(this._executionContext, DispatcherOperation._invokeInSecurityContext, this);
    }
    else
    {
        DispatcherOperation._invokeInSecurityContext(this);
    }
    EventHandler completed;
    lock (this.DispatcherLock)
    {
        this._status = DispatcherOperationStatus.Completed;
        completed = this._completed;
    }
    if (completed !null)
    {
        completed(this, EventArgs.Empty);
    }
    return this._result;
}


Simply put, this method does the following:
  1. Sets the status of the operation to Executing.
  2. Runs the operation on the appropriate execution and security context. If no execution context was specified when the operation was constructed, the security context captured at static initialization time is the only context used (which answers our question from above).
  3. Sets the status of the operation to Completed.
  4. Notifies interested parties that the operation has completed.
  5. Returns the operation's method's result.

Cancellation

If you have a reference to a DispatcherObject, you can cancel its execution by calling its Abort() method. This method defers to the Dispatcher to abort the operation, and if that succeeds notifications are sent to all interested parties. Once an operation starts to execute, it cannot be aborted. DispatcherObject.Abort() returns true if and only if the cancellation is successful.

public bool Abort()
{
    bool flag = false;
    if (this._dispatcher !null)
    {
        flag = this._dispatcher.Abort(this);
        if (flag)
        {
            EventHandler aborted = this._aborted;
            if (aborted !null)
            {
                aborted(this, EventArgs.Empty);
            }
        }
    }
    return flag;
}


Notification

In the two sections above (Invocation and Cancellation), the highlighted methods reference the internal EventHandlers _completed and _aborted to convey status changes to interested parties. These events are publicly available through the Completed and Aborted members of the DispatcherOperation class.

Waiting for Operation Completion

We saw this use case in the last post, but we'll take one last look before wrapping up. DispatcherObject exposes two public signatures for the Wait() method: one that waits indefinitely and another that returns after a specified millisecond timeout. Both overloads of Wait() return a DispatcherOperationStatus value that reflects the resulting state of the operation.

[Beats Antique: Spiderbite -- YouTube]

1 comment:

  1. Regarding the second constructor (Dispatcher dispatcher, Delegate method, DispatcherPriority priority) that you mentioned. I found some code explaining it. It seems that this constructor is used if a BeginInvoke is called during shutdown:

    In class Dispatcher, method BeginInvokeImpl():
    ...
    // Rather than returning null we'll create an aborted operation and return it to the user
    operation = new DispatcherOperation(this, method, priority);
    ...

    ReplyDelete