如何使死锁安全的异步库方法
How to make a deadlock safe async library method
我发布了一个开源 .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();
}
}
}
Is there a deadlock safe way to expose methods that need to do long running tasks, such as DNS lookups?
是:
- 使用
await
而不是 ContinueWith
。
- 对每个
await
使用 ConfigureAwait(false)
。有可用的分析器可以强制执行此操作并确保您不会错误地错过任何一个。
In the example below
示例代码存在缺陷,因为它使用的 UI SynchronizationContext
不进行消息泵送。您应该为 运行 这些测试创建一个实际的 WinForms/WPF 应用程序,然后您会发现死锁行为与此问题中的示例代码不同。
我发布了一个开源 .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();
}
}
}
Is there a deadlock safe way to expose methods that need to do long running tasks, such as DNS lookups?
是:
- 使用
await
而不是ContinueWith
。 - 对每个
await
使用ConfigureAwait(false)
。有可用的分析器可以强制执行此操作并确保您不会错误地错过任何一个。
In the example below
示例代码存在缺陷,因为它使用的 UI SynchronizationContext
不进行消息泵送。您应该为 运行 这些测试创建一个实际的 WinForms/WPF 应用程序,然后您会发现死锁行为与此问题中的示例代码不同。