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

Sync to async dispatch: how can I avoid deadlock?

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

我的问题是:在这种情况下如何避免死锁?

重现步骤:

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

  2. 这个方法创建一个IPlugin的实例然后调用它的方法RequestSomething()

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

  4. 库回调它的方法NotifyNewValueProgressAsync()

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

  6. 由于 UI 上下文被这一行 asyncTarget.MethodFromAsyncLibrary("HelloFromPlugin").Result 阻塞,因此第 5 步中的回调导致死锁。

参见下面的代码示例和相关注释:

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

仅供参考,您还可以在 .

中找到有关此问题的一些背景信息

根据定义,同步方法不会是异步的。您需要使用 TAP 将来自 UI 的同步方法的调用包装在一个任务中,并在那里等待它们,同时使您从异步等待的方法。

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

迫使 你使用最危险和最邪恶的异步同步黑客之一:nested message loop hack (as briefly described in my article on brownfield async). Since this is a WPF app, you'd use a nested dispatcher frame。这个 hack 的主要问题是它在整个 UI 层中引入了可重入性,这是最微妙和最困难的并发问题。