Deadlock when thread uses dispatcher and the main thread is waiting for thread to finish

假如想象 提交于 2019-11-29 11:18:28

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;
}

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 ...;
});

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

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.

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;
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!