c# - 如何使死锁安全的异步库方法
问题描述
我发布了一个开源 .Net 库。是否有一种死锁安全的方法来公开需要执行长时间运行任务的方法,例如 DNS 查找?
在下面的示例中f1
,f2
表示可从 nuget 包访问的库函数。该Task.Delay
调用表示一些长时间运行的网络任务,例如 DNS 查找。库方法f1
和f2
,根据调用者的需要,可能会被调用为 fire and forget 或者他们可能想要等待完成。
我的问题是如何编写库方法以使调用者不存在死锁的风险?
调用旁边的注释显示了哪些死锁。f1
是最好的,但调用者必须意识到需要.ConfigureAwait(false)
.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Deadlock
{
class Program
{
private static Form _form;
public static async Task Main()
{
_form = new Form();
Func<int, Task> f1 = (i) =>
{
Console.WriteLine($"{DateTime.Now.Second}: f1 {i}");
return Task.Delay(1000)
.ContinueWith(t => Console.WriteLine($"{DateTime.Now.Second}: f1 {i} finished"));
};
Func<int, Task> f2 = async (i) =>
{
Console.WriteLine($"{DateTime.Now.Second}: f2 {i}");
await Task.Delay(1000)
.ContinueWith(t => Console.WriteLine($"{DateTime.Now.Second}: f2 {i} finished"));
};
var syncCtx = SynchronizationContext.Current;
Console.WriteLine($"sync ctx ? {syncCtx != null}");
for (int i = 0; i < 3; i++)
{
_ = f1(i); // OK.
//_ = f2(i); // OK.
//await f1(i); // Deadlock.
//await f1(i).ConfigureAwait(false); // OK.
//await f2(i); // Deadlock.
//await f2(i).ConfigureAwait(false); // Deadlock.
Thread.Sleep(1000);
}
Console.WriteLine($"{DateTime.Now.Second}: Finished");
Console.ReadLine();
}
}
}
解决方案
是否有一种死锁安全的方法来公开需要执行长时间运行任务的方法,例如 DNS 查找?
是的:
- 使用
await
而不是ContinueWith
. - 用于
ConfigureAwait(false)
每个await
. 有可用的分析器可以强制执行此操作,并确保您不会错误地错过任何一个。
在下面的例子中
示例代码有缺陷,因为它使用的 UISynchronizationContext
不进行消息泵送。您应该创建一个实际的 WinForms/WPF 应用程序来运行这些测试,然后您会看到死锁行为与此问题中的示例代码不同。
推荐阅读
- java - Hazelcast 实例在访问分布式地图时未激活
- android - 是否可以知道 Alt Beacon 上次在 Android 上扫描信标的时间?
- mongodb - 查找比数据库中特定记录便宜的所有记录
- xcode - Xcode 新构建系统不会构建 SDK pod
- angular - Angular 类封装
- substrate - 使用节点模板创建私有 Polkadot 区块链
- flutter - 颤振从列表中删除项目导致完全重建
- c# - 如何使用 openXml 为特定单元格添加颜色?
- reactjs - react npm build 在选项卡上显示 favicon,但它只是一个空白页面
- google-play - 如何在 Google Play 控制台上导入/导出可用国家/地区?