繁体   English   中英

使用SynchronizationContext时async / await死锁

[英]async/await deadlocking when using a SynchronizationContext

根据这个链接

当您在等待具有await关键字的方法时,编译器会代表您生成大量代码。 此操作的目的之一是处理与UI线程的同步。 钥匙
此功能的组件是SynchronizationContext.Current ,它获取当前线程的同步上下文。
根据具体情况填充SynchronizationContext.Current
你所处的环境。任务的GetAwaiter方法可以查找
SynchronizationContext.Current 如果当前同步上下文不为null,则传递给该awaiter的延续将返回到该同步上下文。

当以阻塞方式使用使用新异步语言功能的方法时,如果是,则最终会出现死锁
你有一个可用的SynchronizationContext 当您以阻塞方式使用此类方法(等待Task with Wait方法或直接从Task的Result属性获取结果)时,您将同时阻止主线程。 当最终任务在线程池中的该方法内完成时,它将调用continuation以回发到主线程,因为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线程吗?

在自己的博客文章中详细解释了这一点,但在此重申......

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

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

但是我仍然无法理解为什么UI线程被阻止了?

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

什么是可用的SynchronizationContext?

当前的SynchronizationContext应该是AspNetSynchronizationContext一个实例, 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<IEnumerable<Car>> GetAsync下载完成后, GetCarsInAWrongWayAsync将继续在该ASP.NET请求上下文上执行并(最终)完成它已返回的任务。

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

最后一点, GetCarsInAWrongWayAsync是一种OK方法。 如果它使用ConfigureAwait(false)会更好,但它实际上并没有 CarsSync是导致死锁的方法; 它调用Task<T>.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);
  }
}

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

有些SynchronizationContext是多线程的,并且不会出现问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM