Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I avoid a race condition when using Dispatcher.Run()?

Tags:

c#

dispatcher

I've found little information on how to properly use the Dispatcher class on its own.

Currently I am using it similar to this question, but there is an inherent race condition which I do not see mentioned anywhere.

Assuming you use the following code to start a dispatcher thread:

Thread thread = new Thread(Dispatcher.Run);
thread.Start();

And try to use it later:

Dispatcher.FromThread(thread).Invoke(MyMethodDelegate);

This will often throw a NullReferenceException as the Dispatcher.FromThread call may return null since there is no guarantee that Dispatcher.Run has been called yet.

What I've done to implement this properly is to use a signal to ensure the dispatcher is running before continuing to use it on the main thread.

like image 474
Travis Avatar asked May 02 '11 16:05

Travis


2 Answers

This is a shorter version, done as a utility function, inspired by yours so I left out the comments.

private static Thread CreateDispatcherThread()
{
    using (var startedEvent = new ManualResetEventSlim()) 
    {
        var dispatcherThread = new Thread( _ => { 
            Dispatcher.CurrentDispatcher.BeginInvoke((Action)(startedEvent.Set));
            Dispatcher.Run(); } );
        dispatcherThread.Start();
        startedEvent.WaitHandle.WaitOne();
        return dispatcherThread;
    }
}   
like image 92
Tony Lee Avatar answered Oct 11 '22 03:10

Tony Lee


Here is what I ended up doing, which is what I believe you need to do in order to use the Dispatcher properly.

private Thread executionThread;
private object SyncObject {get;set;}
private delegate void DispatcherMethod();

private void InitDispatcher()
{
    this.SyncObject = new object();

    // Set up the dispatcher pump.  See Dispatcher.Run on MSDN.
    this.executionThread = new Thread(StartDispatcher);

    lock (this.SyncObject)
    {
        this.executionThread.Start();
        Monitor.Wait(this.SyncObject);
    }
}   


private void StartDispatcher()
{
    DispatcherMethod method = DispatcherStarted;
    // Enqueue a started event by adding an initial method on the message pump.
    // Use BeginInvoke because the dispatcher is not actually running yet.
    // The call to Dispatcher.CurrentDispatcher handles creating the actual
    // Dispatcher instance for the thread (see MSDN - Dispatcher.FromThread
    // does not initialize the Dispatcher).
    Dispatcher.CurrentDispatcher.BeginInvoke(method);
    Dispatcher.Run();
}


private void DispatcherStarted()
{
    lock (this.SyncObject)
    {
        Monitor.Pulse(this.SyncObject);
    }
}

After InitDispatcher returns, you can use

Dispatcher.FromThread(executionThread).Invoke

or

Dispatcher.FromThread(executionThread).BeginInvoke

to marshal calls to the dispatcher thread.

like image 23
Travis Avatar answered Oct 11 '22 01:10

Travis