【问题标题】:Call async method from sync action methods: Task.Run or ConfigureAwaits(false)从同步操作方法调用异步方法:Task.Run 或 ConfigureAwaits(false)
【发布时间】:2017-03-12 18:41:48
【问题描述】:

我可以防止 Result 在控制器的同步操作方法中调用异步任务的死锁:ConfigureAwaits(false) on Task 或使用 Task.Run 。在这两种情况下,异步方法都将在线程池中的线程上完成。控制器来源:

public class TestController : Controller
{
    /// <summary>
    /// Thread from threadpool will be used to finish async method.
    /// </summary>
    public string TaskRun()
    {
        return Task.Run(GetStatus).Result + " <br/> " +
                Thread.CurrentThread.ManagedThreadId + " - " +
                Thread.CurrentThread.IsThreadPoolThread;
    }

    /// <summary>
    /// After completion it will try to finish async method in original thread used to work for httprequest.
    /// </summary>
    public string Deadlock()
    {
        return GetStatus().Result;
    }

    /// <summary>
    /// Thread from threadpool will be used to finish async method.
    /// </summary>
    public string AwaitsFalse()
    {
        return GetStatusAwaitsFalse().Result + " <br/> " +
                Thread.CurrentThread.ManagedThreadId + " - " +
                Thread.CurrentThread.IsThreadPoolThread;
    }

    public async Task<string> PureAsync()
    {
        return await GetStatus() + " <br/> " +
                Thread.CurrentThread.ManagedThreadId + " - " +
                Thread.CurrentThread.IsThreadPoolThread;
    }

    public static async Task<string> GetStatusAwaitsFalse()
    {
        using (var httpClient = new HttpClient())
        {
            var response = await httpClient.GetAsync("http://www.google.com")
                .ConfigureAwait(false);
            return response.StatusCode + " - " + 
                Thread.CurrentThread.ManagedThreadId + " - " +
                Thread.CurrentThread.IsThreadPoolThread;
        }
    }

    public static async Task<string> GetStatus()
    {
        using (var httpClient = new HttpClient())
        {
            var response = await httpClient.GetAsync("http://www.google.com");
            return response.StatusCode + " - " +
                Thread.CurrentThread.ManagedThreadId + " - " +
                Thread.CurrentThread.IsThreadPoolThread;
        }
    }
}

/test/taskrun 的输出(最后两个 int 值是 ManagedThreadId 和 IsThreadPoolThread):

OK - 12 - True 
6 - True

/test/awaitsfalse 的输出是相同的。有什么区别吗?

【问题讨论】:

  • 退后一步:为什么你首先使用同步操作来调用异步方法?
  • @CharlesMager 因为它很有趣。我了解它如何与“纯异步”方法一起使用。现在我正在尝试找出不良案例以更深入地了解它。
  • 此外,根据异步等待的最佳实践,您应该“尽可能使用 ConfigureAwait(false)”(msdn.microsoft.com/en-us/magazine/jj991977.aspx)。它可以防止在 UI 线程上调用 Result 时出现死锁。所以这对我来说很有趣:我可以用另一种方式做同样的事情吗?

标签: c# asynchronous task deadlock


【解决方案1】:

sync-over-async 没有通用的解决方案,只有各种反模式,每个反模式都有不同的问题。详情请见我的MSDN article on brownfield async

ConfigureAwait(false) 的问题在于它必须在任何地方应用——对于被调用方法的传递闭包中的每个await真的很容易错过一个,然后你就有一个等待发生的死锁。例如,上次我检查时,HttpClient.GetAsync 在他们的一个移动平台构建中缺少一个。即使您(以及您的所有依赖项)完美地做到了这一点,随着时间的推移维护也同样困难。

Task.Run 的问题在于它在线程池线程上运行代码。 (明显地)。这对于可以在线程池线程上运行的代码来说很好,但并非所有代码都如此。它的效率也较低,并且(如果过度使用)可能会导致 ASP.NET 上的可伸缩性问题。

附:如果您坚持阻塞,请使用GetAwaiter().GetResult() 而不是Result,因为Result 会将异常包装在AggregateException 中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-09-23
    • 1970-01-01
    • 1970-01-01
    • 2016-04-07
    • 2020-12-19
    • 2016-06-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多