Thursday, April 28, 2011

WPF: Dispatcher - Understanding Frames

Over the last couple of posts, I covered the process through which an application establishes its primary Dispatcher object. I now want to examine how this Dispatcher actually runs. Based on previous experience, I know the Dispatcher sits in a loop dequeuing work and executing it, but how is that loop governed and what types are in play?


We know from previous exploration on this blog that Application.Run() ultimately calls Dispatcher.Run() to "get the loop going". So, is there a loop in that Run() method? Not precisely...
// System.Windows.Threading.Dispatcher
[SecurityCritical]
public static void Run()
{
    Dispatcher.PushFrame(new DispatcherFrame());
}



MSDN says that a DispatcherFrame "[r]epresents an execution loop in the Dispatcher". When I first read this, I jumped to the conclusion that the DispatcherFrame must own the loop itself, expecting that it would define a method with the magic loop in it, but this is not the case. The DispatcherFrame "represents" the loop and provides governing control of the loop, but there is nothing executable about it:
namespace System.Windows.Threading
{
    public class DispatcherFrame : DispatcherObject
    {
        private bool _exitWhenRequested;
        private bool _continue;

        public DispatcherFrame() : this(true) {}
        public DispatcherFrame(bool exitWhenRequested{ ... }

        public bool Continue { getset; }    }
}


There are two governing controls to a DispatcherFrame. At construction time, you can specify whether your frame will "play nice" and exit when the Dispatcher wants to shutdown or whether it will wait for its own termination condition. The Continue property is used to control the loop represented by the DispatcherFrame, but we still haven't discovered where that loop is!

Dispatcher.Run() calls Dispatcher.PushFrame() to push a "default" DispatcherFrame into play. Let's see what this does:
// System.Windows.Threading.Dispatcher
public static void PushFrame(DispatcherFrame frame)
{
    // lots of error checking... not important to our discussion
    Dispatcher currentDispatcher = Dispatcher.CurrentDispatcher;

    currentDispatcher.PushFrameImpl(frame);
}



private void PushFrameImpl(DispatcherFrame frame)
{
    MSG mSG = default(MSG);
    this._frameDepth++;

    try
    {
        // synchronization... not important to our discussion
        try
        {
            // more synchronization... not important to our discussion

           while (frame.Continue && this.GetMessage(ref mSG, IntPtr.Zero, 0, 0)) // aha!
            {
                this.TranslateAndDispatchMessage(ref mSG);
            }


            if (this._frameDepth == 1 && this._hasShutdownStarted)
            {
                this.ShutdownImpl();
            }
        }
        finally
        {

            // even more synchronization... not important to our discussion
        }
    }
    finally
    {
        this._frameDepth--;
        if (this._frameDepth == 0this._exitAllFrames = false;
    }
}



Now we're getting somewhere! When we call PushFrame(), we will get pulled into a while loop that won't exit until the frame no longer needs to continue or until our message source is cut off. In a typical application, we will sit in that loop and process Windows messages until the application wants to shutdown.

This first call to PushFrame() gets everything going, but we can see from the decompiled source that the Dispatcher expects that multiple frames can come into play over time. The most common case for this is when we are in the middle of dispatching a long-running/blocking operation and need to force the Dispatcher to process other items off its queue (to keep the UI responsive, perhaps). We can't really return from the original operation, so we push another frame to get us back to processing the queue in a nested while loop. The documentation for DispatcherFrame on MSDN illustrates this "DoEvents" case and Kent Boogaart takes that example to the next level.

Kent proposed an alternate solution to the DoEvents problem that doesn't seem to require any DispatcherFrames:
public static void DoEvents(this Application application)
{
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background,
new VoidHandler(() => { }));
}

private delegate void VoidHandler();

I thought this was quite curious. We're sitting on the dispatcher's thread and we tell our dispatcher to queue up a do-nothing, background-priority operation and wait for it to complete. I can understand that all higher priority operations in the queue will get worked off before the one we're inserting, but how in the world can the dispatcher get back to processing the queue when we're sitting on its thread waiting for something to happen?


The Dispatcher class has an internal InvokeImpl() method that all flavors of Invoke() ultimately call. The following piece of code in InvokeImpl() forces the wait to happen:


var dispatcherOperation = this.BeginInvokeImpl(priority, method, args, numArgs);
if (dispatcherOperation !null)
{
    dispatcherOperation.Wait(timeout);

    // operation status checks... not important to our discussion
}


On the surface, this is exactly what we would expect. Any Invoke() call can be performed in terms of a BeginInvoke() and DispatcherOperation manipulation. The BeginInvokeImpl() call will get our do-nothing operation on the queue, but where does the queue get processed? Let's see what happens during the call to DispatcherOperation.Wait():
// System.Windows.Threading.DispatcherOperation
public DispatcherOperationStatus Wait(TimeSpan timeout)
{
    if ((this._status == DispatcherOperationStatus.Pending || this._status == DispatcherOperationStatus.Executing)

        && timeout.TotalMilliseconds !0.0)
    {
        if (this._dispatcher.Thread == Thread.CurrentThread)
        {

            // error check... not important to our discussion

            var frame = new DispatcherOperation.DispatcherOperationFrame(this, timeout); // ok then!
            Dispatcher.PushFrame(frame);
        }
        else
        {
             var dispatcherOperationEvent = new DispatcherOperation.DispatcherOperationEvent(this, timeout);
            dispatcherOperationEvent.WaitOne();
        }
    }
    return this._status;
}


This method needs to handle two separate cases:
  • If we are waiting for a DispatcherOperation from a different thread to complete, we need to use something akin to a ManualResetEvent to signal completion across the thread boundary.
  • If we are on the same thread as the operation's dispatcher, we need to call Dispatcher.PushFrame() to get back to servicing the queue.
DispatcherOperation.DispatcherOperationFrame derives from DispatcherFrame and will continue to loop while the operation of interest hasn't completed or aborted. Once the do-nothing operation signals one of these conditions, the inner frame will exit and our Invoke() call will return. The outer frame will take over at that point and keep the loop going.

There is no magic here. Kent's shorter solution is more elegant because it expresses the DoEvents problem in terms of Invoke semantics -- which are easier to understand than frame semantics -- but rest assured that DispatcherFrames are still in play.

[Swedish House Mafia: Miami 2 Ibiza -- YouTube]

References

No comments:

Post a Comment