Thursday, January 27, 2011

DispatcherFrame. Look in-Depth


DispatcherFrame. Look in-Depth

I think that most all of you heard about DispatcherFrame. Let’s try to understand what is it and where it can be used? MSDN said: “Represent an execution loop in the Dispatcher”. Not enough info to understand and use it as I think. Also, there is example of very usefull procedure, but also, not obvious to understand. Here is it:
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public void DoEvents()
{
    DispatcherFrame frame = new DispatcherFrame();
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
        new DispatcherOperationCallback(ExitFrame), frame);
    Dispatcher.PushFrame(frame);
}

public object ExitFrame(object f)
{
    ((DispatcherFrame)f).Continue = false;
    return null;
}

This procedure is DoEvents. As you can guess from it’s title – it force events to be performed. So, let’s  understand DispatcherFrame and how this method works.
Let’s start with Dispatcher class. I think everybody who works with WPF knows what it is. It is a class that works with UI thread in WPF. It contains queue of items that should be performed. I hope that everybody knows about Dispatcher and I don’t need to describe it in details.
To understand how Dispatcher works, I use ildasm.exe utility to disassemble WindowsBase assembly. I found Dispatcher class and look at its IL. There is method Run() which start Dispatcher work. Let’s look inside this method:

IL_0000:newobj instance void System.Windows.Threading.DispatcherFrame ::.ctor()
IL_0005:call void System.Windows.Threading.Dispatcher::PushFrame(class System.Windows.Threading.DispatcherFrame)

New DispatcherFrame is created and PushFrame() method is called. Let’s go to implementation of this method (I include here only important lines):

IL_0039:ldarg.0
IL_0043:call instance bool System.Windows.Threading.Dispatcher::GetMessage(valuetype System.Windows.Interop.MSG&, int, int32, int32)

IL_004d: call instance void System.Windows.Threading.Dispatcher::TranslateAndDispatchMessage(valuetype System.Windows.Interop.MSG&)

IL_0053: callvirt instance bool System.Windows.Threading.DispatcherFrame::get_Continue()
IL_0058:  brtrue.s IL_0039

In a friendly manner it looks like:

While (dispatcherFrame.Continue)
{
                Dispatcher.GetMessage();
                Dispatcher.TranslateAndDispatch();
}

So, while dispatcherFrame’s Continue property is true this cycle of reading system messages will be executed.
Let’s return to the MSDN definition: “Represent an execution loop in the Dispatcher”. Exactly this WHILE statement is represented by DispatcherFrame.
When application is started, Dispatcher.Run() is called. Dispatcher create default DispatcherFrame which represent main message loop in application. You can call Dispatcher.PushFrame(DispatcherFrame frame) to start new loop described above. Main loop will be “paused” until new DispatcherFrame.Continue = false.

Now, lets return to DoEvents() for WPF.  

[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public void DoEvents()
{
    DispatcherFrame frame = new DispatcherFrame();
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
        new DispatcherOperationCallback(ExitFrame), frame);
    Dispatcher.PushFrame(frame);
}

public object ExitFrame(object f)
{
    ((DispatcherFrame)f).Continue = false;
    return null;
}

Let’s skip security attribute and go forward. Say, we have a very huge set of operations that should be performed in UI thread and while this application GUI should be updated. Since our operations are performed in UI thread, GUI will not be updated because Dispatcher will be busy by these operations.
How this problem can be solved?
In DoEvents() there is created new DispatcherFrame (frame variable) and one “Invoke” for dispatcher with Background priority, which set that frame shouldn’t be continued (frame.Continue=false). Background priority is lowest.
When you call DoEvents() there is set of items in Dispatcher that should be performed. Also, this set contains item, which should update our GUI and should be performed ASAP. When PushFrame() is called, main loop suspended and new loop starts (as described above). Dispatcher begin to get system messages AND to process items queue depending on it’s priority. Dispatcher will process all items and one of that items is operation that stop our new loop. It has lowest priority and will be performed last. After operation invoked, new loop stopped and flow returns to main loop and huge operations processing.

No comments:

Post a Comment