SynchronizationContext

SynchronizationContext

根据this link:


    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线程吗?

最佳答案

我用in my own blog post完整地解释了这一点,但在这里重申一下...

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

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



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



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

因此,遍历您的示例:

当请求执行此操作时,CarsSync将在该请求上下文中开始执行。前进至此行:

var cars = client.GetCarsInAWrongWayAsync().Result;

基本上与此相同:
Task<IEnumerable<Car>> carsTask = client.GetCarsInAWrongWayAsync();
var cars = carsTask.Result;

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

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

最后一点,GetCarsInAWrongWayAsync是可以的方法。如果使用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);
  }
}

关于c# - 使用SynchronizationContext时async/await死锁,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34151179/

10-10 15:58