使用SynchronizationContext时async / await死锁 [英] async/await deadlocking when using a SynchronizationContext

查看:157
本文介绍了使用SynchronizationContext时async / await死锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据此链接


当您等待带有await关键字的方法时,编译器会生成一堆代码代表您。
动作的目的之一是处理与UI线程的同步。此功能的关键

组件是 SynchronizationContext.Current
,它获取当前线程的同步上下文。
$ b根据您所处的

环境填充$ b SynchronizationContext.Current GetAwaiter Task的方法查找

SynchronizationContext.Current 。如果当前同步上下文
不为null,则传递给该等待者的继续将
发回到该同步上下文。

When you are awaiting on a method with await keyword, compiler generates bunch of code in behalf of you. One of the purposes of this action is to handle synchronization with the UI thread. The key
component of this feature is the SynchronizationContext.Current which gets the synchronization context for the current thread.
SynchronizationContext.Current is populated depending on the
environment you are in. The GetAwaiter method of Task looks up for
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.

以阻塞方式使用使用新的异步语言功能的方法时,如果

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

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.
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. 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. But there is a problem here: the UI thread is blocked and you have a deadlock!



    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>>();
            }
        }
    }

我很难理解<上面的语句的 strong> bolt 部分,但是当我测试上面的代码时,它如预期那样死锁。
但是我仍然不明白为什么UI线程被阻止了?

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?

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

In this case, what is the available SynchronizationContext? Is it the UI thread?

推荐答案

我在在我自己的博客文章中,但在此重申一下...

I explain this in full in my own blog post, but to reiterate here...

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

await by default will capture a current "context" and resume its async method on that context. This context is SynchronizationContext.Current unless it is null, in which case it is TaskScheduler.Current.

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

Deadlocks can occur when you have a one-thread-at-a-time SynchronizationContext and you block on a task representing asynchronous code (e.g., using Task.Wait or Task<T>.Result). Note that it is the blocking that causes the deadlock, not just the SynchronizationContext; the appropriate resolution (almost always) is to make the calling code asynchronous (e.g., replace Task.Wait/Task<T>.Result with await). This is especially true on ASP.NET.


但是我仍然不明白为什么UI线程被阻止了?

But I still can't understand why the UI thread is blocked?

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

Your example is running on ASP.NET; there is no UI thread.


可用的SynchronizationContext是什么?

what is the available SynchronizationContext?

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

The current SynchronizationContext should be an instance of AspNetSynchronizationContext, a context that represents an ASP.NET request. This context only allows one thread in at a time.

因此,遍历您的示例:

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

When a request comes in for this action, CarsSync will start executing within that request context. It proceeds to this line:

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请求上下文中继续执行 并(最终)完成已经返回的任务。

So, it proceeds to call into GetCarsInAWrongWayAsync, which runs until it hits its first await (the GetAsync call). At this point, GetCarsInAWrongWayAsync captures its current context (the ASP.NET request context) and returns an incomplete 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.

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

However, as soon as GetCarsInAWrongWayAsync returns the incomplete task, CarsSync blocks the current thread, waiting for that task to complete. Note that the current thread is in that ASP.NET request context, so CarsSync will prevent GetCarsInAWrongWayAsync from ever resuming execution, causing the deadlock.

最后一点, GetCarsInAWrongWayAsync 是一种可以接受的方法。最好使用 ConfigureAwait(false),但实际上不是错误 CarsSync 是导致死锁的方法;它调用了 Task< T>。结果 错误。适当的解决方法是更改​​ CarsSync

As a final note, GetCarsInAWrongWayAsync is an OK method. It would be better if it used ConfigureAwait(false), but it's not actually wrong. CarsSync is the method causing the deadlock; it's call to Task<T>.Result is wrong. The appropriate fix is to change 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时async / await死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆