简体   繁体   English

使用SynchronizationContext时async / await死锁

[英]async/await deadlocking when using a SynchronizationContext

According to this link : 根据这个链接

When you are awaiting on a method with await keyword, compiler generates bunch of code in behalf of you. 当您在等待具有await关键字的方法时,编译器会代表您生成大量代码。 One of the purposes of this action is to handle synchronization with the UI thread. 此操作的目的之一是处理与UI线程的同步。 The key 钥匙
component of this feature is the SynchronizationContext.Current which gets the synchronization context for the current thread. 此功能的组件是SynchronizationContext.Current ,它获取当前线程的同步上下文。
SynchronizationContext.Current is populated depending on the 根据具体情况填充SynchronizationContext.Current
environment you are in. The GetAwaiter method of Task looks up for 你所处的环境。任务的GetAwaiter方法可以查找
SynchronizationContext.Current . SynchronizationContext.Current If current synchronization context is not null, the continuation that gets passed to that awaiter will get posted back to that synchronization context. 如果当前同步上下文不为null,则传递给该awaiter的延续将返回到该同步上下文。

When consuming a method, which uses the new asynchronous language features, in a blocking fashion, you will end up with a deadlock if 当以阻塞方式使用使用新异步语言功能的方法时,如果是,则最终会出现死锁
you have an available SynchronizationContext . 你有一个可用的SynchronizationContext When you are consuming such methods in a blocking fashion (waiting on the Task with Wait method or taking the result directly from the Result property of the Task), you will block the main thread at the same time. 当您以阻塞方式使用此类方法(等待Task with Wait方法或直接从Task的Result属性获取结果)时,您将同时阻止主线程。 When eventually the Task completes inside that method in the threadpool, it is going to invoke the continuation to post back to the main thread because SynchronizationContext.Current is available and captured. 当最终任务在线程池中的该方法内完成时,它将调用continuation以回发到主线程,因为SynchronizationContext.Current可用并被捕获。 But there is a problem here: the UI thread is blocked and you have a deadlock! 但这里有一个问题: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>>();
            }
        }
    }

I have trouble understanding the bolded part of the statement above, but when I test the code above, it deadlocks as expected. 我无法理解上面语句的粗体部分,但是当我测试上面的代码时,它会像预期的那样死锁。 But I still can't understand why the UI thread is blocked? 但是我仍然无法理解为什么UI线程被阻止了?

In this case, what is the available SynchronizationContext ? 在这种情况下,可用的SynchronizationContext什么? Is it the UI thread? 它是UI线程吗?

I explain this in full in my own blog post , but to reiterate here... 在自己的博客文章中详细解释了这一点,但在此重申......

await by default will capture a current "context" and resume its async method on that context. 默认情况下, await将捕获当前的“上下文”并在该上下文中恢复其async方法。 This context is SynchronizationContext.Current unless it is null , in which case it is TaskScheduler.Current . 这个上下文是SynchronizationContext.Current除非它是null ,在这种情况下它是TaskScheduler.Current

Deadlocks can occur when you have a one-thread-at-a-time SynchronizationContext and you block on a task representing asynchronous code (eg, using Task.Wait or Task<T>.Result ). 当您具有一次一个线程的SynchronizationContext并且阻止表示异步代码的任务时(例如,使用Task.WaitTask<T>.Result ),可能会发生死锁。 Note that it is the blocking that causes the deadlock, not just the SynchronizationContext ; 请注意,阻塞会导致死锁,而不仅仅是SynchronizationContext ; the appropriate resolution (almost always) is to make the calling code asynchronous (eg, replace Task.Wait / Task<T>.Result with await ). 适当的解决方案(几乎总是)是使调用代码异步(例如,用await替换Task.Wait / Task<T>.Result Task.Wait )。 This is especially true on ASP.NET. 在ASP.NET上尤其如此。

But I still can't understand why the UI thread is blocked? 但是我仍然无法理解为什么UI线程被阻止了?

Your example is running on ASP.NET; 您的示例在ASP.NET上运行; there is no UI thread. 没有UI线程。

what is the available SynchronizationContext? 什么是可用的SynchronizationContext?

The current SynchronizationContext should be an instance of AspNetSynchronizationContext , a context that represents an ASP.NET request. 当前的SynchronizationContext应该是AspNetSynchronizationContext一个实例, AspNetSynchronizationContext是一个表示ASP.NET请求的上下文。 This context only allows one thread in at a time. 此上下文一次只允许一个线程。


So, walking through your example: 所以,走过你的例子:

When a request comes in for this action, CarsSync will start executing within that request context. 当请求进入此操作时, CarsSync将在该请求上下文中开始执行。 It proceeds to this line: 它继续到这一行:

var cars = client.GetCarsInAWrongWayAsync().Result;

which is essentially the same as this: 这基本上与此相同:

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

So, it proceeds to call into GetCarsInAWrongWayAsync , which runs until it hits its first await (the GetAsync call). 因此,它继续调用GetCarsInAWrongWayAsync ,它一直运行直到它遇到它的第一个awaitGetAsync调用)。 At this point, GetCarsInAWrongWayAsync captures its current context (the ASP.NET request context) and returns an incomplete Task<IEnumerable<Car>> . 此时, GetCarsInAWrongWayAsync捕获其当前上下文(ASP.NET请求上下文)并返回不完整的Task<IEnumerable<Car>> When the GetAsync download finishes, GetCarsInAWrongWayAsync will resume executing on that ASP.NET request context and (eventually) complete the task it already returned. GetAsync下载完成后, GetCarsInAWrongWayAsync将继续在该ASP.NET请求上下文上执行并(最终)完成它已返回的任务。

However, as soon as GetCarsInAWrongWayAsync returns the incomplete task, CarsSync blocks the current thread, waiting for that task to complete. 但是,只要GetCarsInAWrongWayAsync返回未完成的任务, CarsSync阻止当前线程,等待该任务完成。 Note that the current thread is in that ASP.NET request context, so CarsSync will prevent GetCarsInAWrongWayAsync from ever resuming execution, causing the deadlock. 请注意,当前线程位于该ASP.NET请求上下文中,因此CarsSync将阻止GetCarsInAWrongWayAsync恢复执行,从而导致死锁。

As a final note, GetCarsInAWrongWayAsync is an OK method. 最后一点, GetCarsInAWrongWayAsync是一种OK方法。 It would be better if it used ConfigureAwait(false) , but it's not actually wrong . 如果它使用ConfigureAwait(false)会更好,但它实际上并没有 CarsSync is the method causing the deadlock; CarsSync是导致死锁的方法; it's call to Task<T>.Result is wrong. 它调用Task<T>.Result 错误的。 The appropriate fix is to change CarsSync : 适当的解决方法是更改CarsSync

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

The key point is that some SynchronizationContext s only allow a single thread to run code at the same time. 关键是一些SynchronizationContext只允许单个线程同时运行代码。 One thread is calling Result or Wait . 一个线程正在调用ResultWait When the async methods wants to enter it can't. 当异步方法想要输入它时不能。

Some SynchronizationContext s are mutli-threaded and the problem does not occur. 有些SynchronizationContext是多线程的,并且不会出现问题。

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

相关问题 使用async / await时,使用自定义SynchronizationContext序列化执行 - Using a custom SynchronizationContext to serialize execution when using async/await 使用await时不流动SynchronizationContext - SynchronizationContext is not flowed when using await 等待不使用当前的SynchronizationContext - await not using current SynchronizationContext 在另一个AppDomain中调用Await时没有SynchronizationContext - No SynchronizationContext when calling Await in a another AppDomain 获取异步结果死锁(尽管将配置await设置为false) - Getting async result deadlocking (despite setting configure await to false) C#等待异步方法清除SynchronizationContext(带有repro测试) - C# await in async method clears SynchronizationContext (with repro tests) async / await with ConfigureAwait的continueOnCapturedContext参数和SynchronizationContext用于异步延续 - async/await with ConfigureAwait's continueOnCapturedContext parameter and SynchronizationContext for asynchronous continuations 使用Autofac解决对象时,即使使用ConfigurationAwait(false)也会在Async上发生死锁 - Deadlocking on Async even with ConfigurationAwait(false) when resolving object with Autofac 使用async / await时,GUI会冻结 - GUI freezes when using async/await 使用 async / await 时如何对 await 生成的任务进行优先级排序 - How to prioritize tasks generated by await when using async / await
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM