[英]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 theSynchronizationContext.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. TheGetAwaiter
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 availableSynchronizationContext
.你有一个可用的
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.Wait
或Task<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
,它一直运行直到它遇到它的第一个await
( GetAsync
调用)。 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
. 一个线程正在调用
Result
或Wait
。 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.