首页 > 解决方案 > 如何使死锁安全的异步库方法

问题描述

我发布了一个开源 .Net 库。是否有一种死锁安全的方法来公开需要执行长时间运行任务的方法,例如 DNS 查找?

在下面的示例中f1f2表示可从 nuget 包访问的库函数。该Task.Delay调用表示一些长时间运行的网络任务,例如 DNS 查找。库方法f1f2,根据调用者的需要,可能会被调用为 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();
        }
    }
}

标签: c#.netasync-awaittask-parallel-library

解决方案


是否有一种死锁安全的方法来公开需要执行长时间运行任务的方法,例如 DNS 查找?

是的:

  1. 使用await而不是ContinueWith.
  2. 用于ConfigureAwait(false)每个await. 有可用的分析器可以强制执行此操作,并确保您不会错误地错过任何一个。

在下面的例子中

示例代码有缺陷,因为它使用的 UISynchronizationContext不进行消息泵送。您应该创建一个实际的 WinForms/WPF 应用程序来运行这些测试,然后您会看到死锁行为与此问题中的示例代码不同。


推荐阅读