同步到异步调度:如何避免死锁?

时间:2016-08-29 15:27:22

标签: c# .net wpf async-await deadlock

我试图创建一个具有同步方法的类,并调用其他一些异步的库方法。出于这个原因,我使用Task.Result等待异步操作完成。我的方法由WPF app以同步方式调用。这导致僵局。我知道最好的方法是使我的所有方法都异步,但我的情况要求它们是同步的。另一方面,他们使用其他异步库。

我的问题是:如何在这种情况下避免陷入僵局?

重现的步骤:

  1. 用户点击应用中的按钮(方法Button1_OnClick

  2. 此方法创建IPlugin的实例,然后调用其方法RequestSomething()

  3. 然后,此方法以这种方式调用异步库:asyncTarget.MethodFromAsyncLibrary("HelloFromPlugin").Result

  4. 该库回调其方法NotifyNewValueProgressAsync()

  5. NotifyNewValueProgressAsync()将回调委托给WPF应用程序

  6. 由于此行asyncTarget.MethodFromAsyncLibrary("HelloFromPlugin").Result阻止了UI上下文,因此步骤5中的回调会导致死锁。

  7. 请参阅下面的代码示例和相关注释:

    public class SyncAdapterPlugin : IPlugin, IProgressAsyncHandler
    {
        //Constructor and fields are omitted here
    
        //This method is called from UI context by WPF application and it delegates synchronous call to asynchronous method
        string IPlugin.RequestSomething()
        {
            //In order to be able to run the callback I need to capture current UI context
            _context = TaskScheduler.FromCurrentSynchronizationContext();
    
            var asyncTarget = new ClassFromMyLibrary1(this);
            var resultFromAsyncLibrary = asyncTarget.MethodFromAsyncLibrary("HelloFromPlugin").Result; //Deadlock here!
            return resultFromAsyncLibrary;
        }
    
        //This method does opposite, it delegates asynchronous callback to synchronous
        async Task<bool> IProgressAsyncHandler.NotifyNewValueProgressAsync(string message)
        {
            //NotifyNewValueProgress method is implemented by WPF application and will update UI elements.
            //That's why it's needed to run the callback on captured UI context.
            Func<bool> work = () => _syncProgressHandler.NotifyNewValueProgress(message);
            if (_context != null)
            {
                return await
                    Task.Factory.StartNew(work, CancellationToken.None, TaskCreationOptions.None, _context)
                    .ConfigureAwait(false);
            }
            return work();
        }
    }
    

    完整的代码示例在https://dotnetfiddle.net/i48sRc

    仅供参考,有关此问题的一些背景信息,您也可以在this SO question找到。

2 个答案:

答案 0 :(得分:1)

插件框架存在根本缺陷。特别是,它需要同步 RequestSomething,希望能够调用NotifyNewValueProgressAsync来更新UI。但是,当UI线程运行同步方法时,无法显示UI更新。

这个强制你使用最危险和最邪恶的同步异步黑客攻击之一:nested message loop hack(正如我在blackfield async上的文章中简要描述的那样)。由于这是一个WPF应用,因此您使用nested dispatcher frame。这个黑客的主要痛苦在于它在整个UI层引入了重入,这是最微妙和最困难的并发问题。

答案 1 :(得分:0)

根据定义,同步方法不会是异步的。您将需要在使用TAP的Task中从UI中调用同步方法,并在等待异步时等待它们。