Thursday, May 5, 2011

WPF: Dispatcher - Managing Timers

In my last post, we took a look at the anatomy of the DispatcherTimer and discovered that much of the magic behind its operation is performed by the Dispatcher that the timer runs on. We will devote this post to the exploration of the Dispatcher's timer management facilities.

Consider the following requirements/goals of the DispatcherTimer subsystem:
  • A single Dispatcher can support any number of DispatcherTimers.
  • Each DispatcherTimer may fire at a different interval.
  • A DispatcherOperation representing a tick event cannot be handled on a normal work queue; there needs to be a timing component that determines when execution occurs.
  • The framework should minimize the system resources required to manage all of the timers for a given Dispatcher thread.
Let's see how the Dispatcher class addresses these needs.

Adding/Removing Timers

When a DispatcherTimer starts or stops, it calls Dispatcher.AddTimer() or Dispatcher.RemoveTimer() to adjust the Dispatcher's temporally-based management concerns. (These methods need to be declared internal so DispatcherTimer can access them.)

internal object _instanceLock = new object();
private List<DispatcherTimer> _timers = new List<DispatcherTimer>();
private bool _hasShutdownFinished;
private long _timersVersion;

internal void AddTimer(DispatcherTimer timer)
{
    lock (this._instanceLock)
    {
        if (!this._hasShutdownFinished)
        {
            this._timers.Add(timer);
            this._timersVersion +1L;
        }
    }
    this.UpdateWin32Timer();
}

internal void RemoveTimer(DispatcherTimer timer)
{
    lock (this._instanceLock)
    {
        if (!this._hasShutdownFinished)
        {
            this._timers.Remove(timer);
            this._timersVersion +1L;
        }
    }
    this.UpdateWin32Timer();
}


The _timers list is protected by the dispatcher's _instanceLock, and can cannot be modified once dispatcher shutdown has finished. After the timer is added or removed from the list, the _timersVersion one-up counter is incremented to indicate that we have a new timer configuration. After the _instanceLock is released (and oddly in all circumstances, even when no change is made to the _timers list), both methods call Dispatcher.UpdateWin32Timer() to revisit when the next tick should be generated.

Determining When to Fire

The UpdateWin32Timer() method makes sure the update request happens on the correct thread, dispatching to UpdateWin32TimerFromDispatcherThread() to get the real work done. This latter method figures out when the next timer should fire:

internal void UpdateWin32Timer()
{
    if (this.CheckAccess())
    {
        this.UpdateWin32TimerFromDispatcherThread(null);
        return;
    }


    this.BeginInvoke(DispatcherPriority.Send, new DispatcherOperationCallback(

        this.UpdateWin32TimerFromDispatcherThread), null);
}


private object UpdateWin32TimerFromDispatcherThread(object unused)
{
    lock (this._instanceLock)
    {
        if (!this._hasShutdownFinished)
        {
            bool dueTimeFound = this._dueTimeFound;
            int dueTimeInTicks = this._dueTimeInTicks;
            this._dueTimeFound = false;
            this._dueTimeInTicks = 0;
            if (this._timers.Count > 0)
            {
                for (int i = 0; i < this._timers.Count; i++)
                {
                    DispatcherTimer dispatcherTimer = this._timers[i];
                    if (!this._dueTimeFound || dispatcherTimer._dueTimeInTicks - this._dueTimeInTicks < 0)
                    {
                        this._dueTimeFound = true;
                        this._dueTimeInTicks = dispatcherTimer._dueTimeInTicks;
                    }
                }
            }
            if (this._dueTimeFound)
            {
                if (!this._isWin32TimerSet || !dueTimeFound || dueTimeInTicks !this._dueTimeInTicks)
                {
                    this.SetWin32Timer(this._dueTimeInTicks);
                }
            }
            else
            {
                if (dueTimeFound)
                {
                    this.KillWin32Timer();
                }
            }
        }
    }
    return null;
}


The UpdateWin32TimerFromDispatcherThread() method acquires the _instanceLock and performs the same shutdown check we saw in AddTimer() and RemoveTimer(), so we know that no changes to the _timers list can happen while we are examining the complete timer situation. This method loops over all of the timers known to the dispatcher to find the one with the soonest "due time in ticks". If the next "due time" represents a change from the previous due time, the dispatcher calls SetWin32Timer() with the new preferred due time. If no due time is found and this represents a change from the dispatcher's previous state, the dispatcher calls KillWin32Timer() to disable the Win32 timer.

Simply put, UpdateWin32TimerFromDispatcherThread() determines when the Win32 timer managed by this thread should go off so that the DispatcherTimer expecting the next tick event can be serviced on time.

Managing the Win32 Timer

UpdateWin32TimerFromDispatcherThread() uses SetWin32Timer() and KillWin32Timer() to manage the next "fire" time. Let's see how these methods work:

private void SetWin32Timer(int dueTimeInTicks)
{
    if (!this.IsWindowNull())
    {
        int num = dueTimeInTicks - Environment.TickCount;
        if (num < 1)
        {
            num = 1;
        }
        SafeNativeMethods.SetTimer(new HandleRef(this, this._window.Value.Handle), 2, num);
        this._isWin32TimerSet = true;
    }
}


private void KillWin32Timer()
{
    if (!this.IsWindowNull())
    {
        SafeNativeMethods.KillTimer(new HandleRef(this, this._window.Value.Handle), 2);
        this._isWin32TimerSet = false;
    }
}


Now we're getting into some real nuts and bolts. These methods are using Win32 calls to activate and deactivate the timer for the _window associated with this dispatcher. So where does that _window come from? And how would the Win32 events get handled once they fire? Let's look at the constructor for the Dispatcher to get some answers (thankfully, there is only a default constructor to consider):

private Dispatcher()
{
    // ignoring details not relevant to this discussion
    MessageOnlyHwndWrapper value = new MessageOnlyHwndWrapper();
    this._window = new SecurityCriticalData<MessageOnlyHwndWrapper>(value);
    this._hook = new HwndWrapperHook(this.WndProcHook);
    this._window.Value.AddHook(this._hook);
}

The Dispatcher creates a MessageOnlyHwndWrapper to serve as its "window"; this wrapper has no visual representation, but is able to process events on a Win32 event loop. The constructor also adds a hook to this window's loop to process the dispatcher's queue and timer-related events. When a timer event comes in, the hook calls Dispatcher.PromoteTimers() -- passing it the current Environment.TickCount -- to see what timers need to be serviced:

private void PromoteTimers(int currentTimeInTicks)
{
    try
    {
        List<DispatcherTimer> list = null;
        long num = 0L;
        lock (this._instanceLock)
        {
            if (!this._hasShutdownFinished && this._dueTimeFound && this._dueTimeInTicks - currentTimeInTicks <0)
            {
                list = this._timers;
                num = this._timersVersion;
            }
        }
        if (list !null)
        {
            DispatcherTimer dispatcherTimer = null;
            int i = 0;
            do
            {
                lock (this._instanceLock)
                {
                    dispatcherTimer = null;
                    if (num !this._timersVersion)
                    {
                        num = this._timersVersion;
                        i = 0;
                    }
                    while (< this._timers.Count)
                    {
                        if (list[i]._dueTimeInTicks - currentTimeInTicks <0)
                        {
                            dispatcherTimer = list[i];
                            list.RemoveAt(i);
                            break;
                        }
                        i++;
                    }
                }
                if (dispatcherTimer !null)
                {
                    dispatcherTimer.Promote();
                }
            }
            while (dispatcherTimer !null);
        }
    }
    finally
    {
        this.UpdateWin32Timer();
    }
}

At the core of this method, the dispatcher tries to find all of the timers that have elapsed, removing each one from the _timers list and then calling Promote() to activate their DispatcherOperation objects. These pre-queued operations are then candidates for execution on the next queue processing sweep. The DispatcherTimer objects will re-register themselves with the Dispatcher (if necessary) as they execute their FireTick() methods.

Putting It All Together

When we look back at the DispatcherHooks exercise from a couple of posts ago, we can now make some sense of the DispatcherTimer-related output:

(1) Posted          Inactive        System.Windows.Threading.DispatcherTimer.FireTick
(2) ...
(3) Inactive       
(4) PriorityChanged Inactive        System.Windows.Threading.DispatcherTimer.FireTick
(5) Completed       Background      System.Windows.Threading.DispatcherTimer.FireTick

When the DispatcherTimer is enabled, it queues an inactive DispatcherOperation in its Restart() method [1]. Time elapses [2] -- the dispatcher itself may go inactive [3] -- but after a while, the dispatcher receives a Win32 timer-related event in its WndProcHook() method, dispatching it to PromoteTimers() to determine which timers need servicing. PromoteTimers() calls the Promote() method on each elapsed timer, which brings about the change in priority of the DispatcherOperation [4]. On the next queue processing cycle, the dispatcher executes the newly-activated operation [5].

[PANTyRAiD: Get the Money -- YouTube]

No comments:

Post a Comment