【问题标题】:async/await deadlocking when using a SynchronizationContext使用 SynchronizationContext 时出现异步/等待死锁
【发布时间】:2015-12-08 21:07:49
【问题描述】:

根据this link

当您使用 await 关键字等待方法时,编译器会代表您生成一堆代码。这样做的目的之一 action 是处理与 UI 线程的同步。关键
此功能的组件是SynchronizationContext.Current 它获取当前线程的同步上下文。
SynchronizationContext.Current 的填充取决于
您所在的环境。Task 的GetAwaiter 方法查找
SynchronizationContext.Current。如果当前同步上下文 不为空,传递给该等待者的延续将 回发到该同步上下文。

当以阻塞方式使用使用新异步语言特性的方法时,如果
您有可用的 SynchronizationContext
当您在 以阻塞方式使用此类方法(等待任务 使用 Wait 方法或直接从 Result 中获取结果 任务的属性),您将同时阻塞主线程 时间。当最终任务在该方法内完成时 线程池,它将调用延续回发到 主线程因为SynchronizationContext.Current 是 可用和捕获。但是这里有个问题:UI线程 被阻止了,你有一个死锁!

    public class HomeController : Controller
    {    
        public ViewResult CarsSync() 
        {
            SampleAPIClient client = new SampleAPIClient();
            var cars = client.GetCarsInAWrongWayAsync().Result;
            return View("Index", model: cars);
        }
    }

    public class SampleAPIClient 
    {
        private const string ApiUri = "http://localhost:17257/api/cars";
        public async Task<IEnumerable<Car>> GetCarsInAWrongWayAsync()
        {
            using (var client = new HttpClient()) 
            {
                var response = await client.GetAsync(ApiUri);

                // Not the best way to handle it but will do the work for demo purposes
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsAsync<IEnumerable<Car>>();
            }
        }
    }

我无法理解上面语句的 粗体 部分,但是当我测试上面的代码时,它会按预期死锁。 但是我还是不明白为什么UI线程被阻塞了?

在这种情况下,可用的SynchronizationContext 是什么?是 UI 线程吗?

【问题讨论】:

  • 这是来自here 吗?如果是这样,您最好在问题中包含指向它的链接,并指出其中大部分是引用。
  • 这不是您问题的直接答案,但是如果您将控制器操作结果设为Task&lt;ViewResult&gt;,它可以一直异步(就 ASP.NET 线程使用而言,它可以更好地扩展)
  • @Damien_The_Unbeliever 是的,我添加了链接。谢谢
  • 将引用文本设为“引用”,因此很明显您引用了该网站
  • @Groo 我很抱歉。我的英语不好。谢谢你的帮助。

标签: c# .net asynchronous


【解决方案1】:

我在in my own blog post 中完整解释了这一点,但在此重申...

await 默认情况下将捕获当前“上下文”并在该上下文中恢复其async 方法。这个上下文是SynchronizationContext.Current,除非它是null,在这种情况下它是TaskScheduler.Current

当您有一个线程一次 SynchronizationContext 并且您阻塞代表异步代码的任务(例如,使用 Task.WaitTask&lt;T&gt;.Result)时,可能会发生死锁.请注意,导致死锁的是阻塞,而不仅仅是SynchronizationContext;适当的解决方案(几乎总是)是使调用代码异步(例如,将Task.Wait/Task&lt;T&gt;.Result 替换为await)。在 ASP.NET 上尤其如此。

但是我还是不明白为什么UI线程被阻塞了?

您的示例在 ASP.NET 上运行;没有 UI 线程。

什么是可用的 SynchronizationContext?

当前的SynchronizationContext 应该是AspNetSynchronizationContext 的一个实例,它是一个表示ASP.NET 请求的上下文。此上下文一次只允许一个线程进入。


所以,看看你的例子:

当针对此操作的请求进入时,CarsSync 将开始在该请求上下文中执行。它继续到这一行:

var cars = client.GetCarsInAWrongWayAsync().Result;

与此基本相同:

Task<IEnumerable<Car>> carsTask = client.GetCarsInAWrongWayAsync();
var cars = carsTask.Result;

因此,它继续调用GetCarsInAWrongWayAsync,直到它遇到第一个awaitGetAsync 调用)。此时,GetCarsInAWrongWayAsync 捕获其当前上下文(ASP.NET 请求上下文)并返回一个不完整的Task&lt;IEnumerable&lt;Car&gt;&gt;。当GetAsync 下载完成时,GetCarsInAWrongWayAsync 将继续在该 ASP.NET 请求上下文中执行并(最终)完成它已经返回的任务。

但是,一旦GetCarsInAWrongWayAsync 返回未完成的任务,CarsSync 就会阻塞当前线程,等待该任务完成。请注意,当前线程位于该 ASP.NET 请求上下文中,因此 CarsSync 将阻止 GetCarsInAWrongWayAsync 继续执行,从而导致死锁。

最后一点,GetCarsInAWrongWayAsync 是一种不错的方法。如果它使用ConfigureAwait(false) 会更好,但它实际上并没有错误CarsSync 是导致死锁的方法;它对Task&lt;T&gt;.Result 的调用是错误的。适当的解决方法是更改​​CarsSync

public class HomeController : Controller
{    
  public async Task<ViewResult> CarsSync() 
  {
    SampleAPIClient client = new SampleAPIClient();
    var cars = await client.GetCarsInAWrongWayAsync();
    return View("Index", model: cars);
  }
}

【讨论】:

  • 在阅读了另一篇 Stephen article 之后,我更清楚地了解了 async/await 导致死锁的原因。
【解决方案2】:

关键是有些SynchronizationContexts只允许单个线程同时运行代码。一个线程正在调用ResultWait。当异步方法想进入时它不能。

一些SynchronizationContexts 是多线程的,不会出现问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-24
    • 1970-01-01
    • 2016-12-22
    • 2021-09-18
    相关资源
    最近更新 更多