使用SynchronizationContext时async/await死锁

nai*_*igu 9 .net c# asynchronous

根据这个链接:

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

当使用阻塞方式使用新异步语言功能的方法时,如果
有可用的话,最终会出现死锁SynchronizationContext.
当您以阻塞方式使用此类方法(等待Task with Wait方法或直接从Task的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>>();
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

我无法理解上面语句的粗体部分,但是当我测试上面的代码时,它会像预期的那样死锁.但是我仍然无法理解为什么UI线程被阻止了?

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

Ste*_*ary 12

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

await默认情况下,将捕获当前的"上下文"并async在该上下文中恢复其方法.SynchronizationContext.Current除非是这种情况,否则就是null这种情况TaskScheduler.Current.

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

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

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

什么是可用的SynchronizationContext?

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


所以,走过你的例子:

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

var cars = client.GetCarsInAWrongWayAsync().Result;
Run Code Online (Sandbox Code Playgroud)

这基本上与此相同:

Task<IEnumerable<Car>> carsTask = client.GetCarsInAWrongWayAsync();
var cars = carsTask.Result;
Run Code Online (Sandbox Code Playgroud)

因此,它继续调用GetCarsInAWrongWayAsync,直到它遇到它的第一个await(GetAsync调用).此时,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);
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 在阅读另一篇 Stephen [文章](https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console 后,我更清楚地了解了 async/await 造成死锁的原因-应用/)。 (2认同)