同步到异步调度:如何避免死锁?
Sync to async dispatch: how can I avoid deadlock?
我正在尝试创建一个 class,它具有同步方法并调用其他一些异步的库方法。出于这个原因,我使用 Task.Result
来等待异步操作完成。我的方法由 WPF 应用程序以同步方式调用。这会导致僵局。我知道最好的方法是让我的所有方法都异步,但我的情况要求它们是同步的。另一方面,他们使用其他异步库。
我的问题是:在这种情况下如何避免死锁?
重现步骤:
用户点击应用中的按钮(方法 Button1_OnClick
)
这个方法创建一个IPlugin
的实例然后调用它的方法RequestSomething()
此方法然后以这种方式调用异步库:asyncTarget.MethodFromAsyncLibrary("HelloFromPlugin").Result
库回调它的方法NotifyNewValueProgressAsync()
NotifyNewValueProgressAsync()
将调用委托回 WPF 应用程序
由于 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 层中引入了可重入性,这是最微妙和最困难的并发问题。
我正在尝试创建一个 class,它具有同步方法并调用其他一些异步的库方法。出于这个原因,我使用 Task.Result
来等待异步操作完成。我的方法由 WPF 应用程序以同步方式调用。这会导致僵局。我知道最好的方法是让我的所有方法都异步,但我的情况要求它们是同步的。另一方面,他们使用其他异步库。
我的问题是:在这种情况下如何避免死锁?
重现步骤:
用户点击应用中的按钮(方法
Button1_OnClick
)这个方法创建一个
IPlugin
的实例然后调用它的方法RequestSomething()
此方法然后以这种方式调用异步库:
asyncTarget.MethodFromAsyncLibrary("HelloFromPlugin").Result
库回调它的方法
NotifyNewValueProgressAsync()
NotifyNewValueProgressAsync()
将调用委托回 WPF 应用程序由于 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 层中引入了可重入性,这是最微妙和最困难的并发问题。