4

Can someone please explain why this creates a deadlock, and how to solve it?

        txtLog.AppendText("We are starting the thread" + Environment.NewLine);

        var th = new Thread(() =>
        {

            Application.Current.Dispatcher.Invoke(new Action(() => // causes deadlock
            {
                txtLog.AppendText("We are inside the thread" + Environment.NewLine); // never gets printed
                // compute some result...
            }));


        });

        th.Start();
        th.Join(); // causes deadlock
        // ... retrieve the result computed by the thread

Explanation: I need my secondary thread to compute a result, and to return it to the main thread. But the secondary thread must also write debug informations to the log; and the log is in a wpf window, so the thread needs to be able to use the dispatcher.invoke(). But the moment I do Dispatcher.Invoke, a deadlock occurs, because the main thread is waiting for the secondary thread to finish, because it needs the result.

I need a pattern to solve this. Please help me rewrite this code. (Please write actual code, do not just say "use BeginInvoke"). Thank you.

Also, theoretically, I don't understand one thing: a deadlock can only happen when two threads access two shared resources in different orders. But what are the actual resources in this case? One is the GUI. But what is the other? I can't see it.

And the deadlock is usually solved by imposing the rule that the threads can only lock the resources in a precise order. I've done this already elsewhere. But how can I impose this rule in this case, since I don't understand what the actual resources are?

seguso
  • 2,024
  • 2
  • 18
  • 20

5 Answers5

6

Short answer: use BeginInvoke() instead of Invoke(). Long answer change your approach: see the altenative.

Currently your Thread.Join() is causing that main thread get blocked waiting for the termination of secondary thread, but secondary thread is waiting to main thread executes your AppendText action, thus your app is deadlocked.

If you change to BeginInvoke() then your seconday thread will not wait until main thread executes your action. Instead of this, it will queue your invocation and continues. Your main thread will not blocked on Join() because your seconday thread this time ends succesfully. Then, when main thread completes this method will be free to process the queued invocation to AppendText

Alternative:

void DoSomehtingCool()
{
    var factory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
    factory.StartNew(() =>
    {
        var result = await IntensiveComputing();
        txtLog.AppendText("Result of the computing: " + result);
    });
}

async Task<double> IntensiveComputing()
{
    Thread.Sleep(5000);
    return 20;
}
2

This deadlock happens because the UI thread is waiting for the background thread to finish, and the background thread is waiting for the UI thread to become free.

The best solution is to use async:

var result = await Task.Run(() => { 
    ...
    await Dispatcher.InvokeAsync(() => ...);
    ...
    return ...;
});
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 1
    Im not even sure a new thread is neccassery in this case at all – Yuval Itzchakov Jun 13 '14 at 19:20
  • @YuvalItzchakov: He presumably (and hopefully) doesn't want to block the UI. – SLaks Jun 13 '14 at 19:27
  • No; the stuff in the background thread shouldn't block the UI. – SLaks Jun 13 '14 at 19:42
  • Thank you, very interesting. I am not familiar with await and InvokeAsync, so it is not immediately clear to me if this works. I'll try to understand this. Btw, InvokeAsync is only in framework 4.5. Is there something for .NET 4.0? – seguso Jun 13 '14 at 19:46
  • 1
    @seguso: You can fake it with Microsoft.Bcl.Async and an extension method that uses a `TaskCompletionSource`. – SLaks Jun 13 '14 at 19:57
1

The Dispatcher is trying to execute work in the UI message loop, but that same loop is currently stuck on th.Join, hence they are waiting on each other and that causes the deadlock.

If you start a Thread and immediately Join on it, you definitely have a code smell and should re-think what you're doing.

If you want things to be done without blocking the UI you can simply await on InvokeAsync

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
1

I had a similar problem which I finally solved in this way:

do{
    // Force the dispatcher to run the queued operations 
    Dispatcher.CurrentDispatcher.Invoke(delegate { }, DispatcherPriority.ContextIdle);
}while(!otherthread.Join(1));

This produces a Join that doesn't block because of GUI-operations on the other thread.

The main trick here is the blocking Invoke with an empty delegate (no-operation), but with a priority setting that is less than all other items in the queue. That forces the dispatcher to work through the entire queue. (The default priority is DispatcherPriority.Normal = 9, so my DispatcherPriority.ContextIdle = 3 is well under.)

The Join() call uses a 1 ms time out, and re-empties the dispatcher queue as long as the join isn't successful.

UML
  • 146
  • 8
  • Simply switch to BeginInvoke(), don't kill the poor CPU. – Tanveer Badar Jul 30 '17 at 18:10
  • @TanveerBadar: Please read my description; "The main trick here is the blocking `Invoke`...". The blocking itself does not consume CPU power. By **not** blocking, you will consume CPU power looping in the do-while loop. – UML Oct 06 '17 at 20:54
  • `Join()` operations are evil; ancient and are surpassed by better programming practices and API alternatives –  Mar 08 '18 at 23:27
1

I really liked @user5770690 answer. I created an extension method that guarantees continued "pumping" or processing in the dispatcher and avoids deadlocks of this kind. I changed it slightly but it works very well. I hope it helps someone else.

    public static Task PumpInvokeAsync(this Dispatcher dispatcher, Delegate action, params object[] args)
    {
        var completer = new TaskCompletionSource<bool>();

        // exit if we don't have a valid dispatcher
        if (dispatcher == null || dispatcher.HasShutdownStarted || dispatcher.HasShutdownFinished)
        {
            completer.TrySetResult(true);
            return completer.Task;
        }

        var threadFinished = new ManualResetEvent(false);
        ThreadPool.QueueUserWorkItem(async (o) =>
        {
            await dispatcher?.InvokeAsync(() =>
            {
                action.DynamicInvoke(o as object[]);
            });
            threadFinished.Set();
            completer.TrySetResult(true);
        }, args);

        // The pumping of queued operations begins here.
        do
        {
            // Error condition checking
            if (dispatcher == null || dispatcher.HasShutdownStarted || dispatcher.HasShutdownFinished)
                break;

            try
            {
                // Force the processing of the queue by pumping a new message at lower priority
                dispatcher.Invoke(() => { }, DispatcherPriority.ContextIdle);
            }
            catch
            {
                break;
            }
        }
        while (threadFinished.WaitOne(1) == false);

        threadFinished.Dispose();
        threadFinished = null;
        return completer.Task;
    }
Mario
  • 659
  • 7
  • 12