I'm calling an async
library method with .ConfigureAwait(false)
. But, I still end up with deadlock. (I'm using it in ASP.NET controller API)
But, if I use the same method wrapped into Task.Run()
it works fine.
My understanding is, if the libraries method is not using ConfigureAwait
internally then adding ConfigureAwait
won't solve the problem as in the library call it will result in deadlock (we block on it by using .Result
). But, if that's the case why does it work in Task.Run()
as it will fail to continue in same context/thread.
This article talks about it. Btw, I have read plenty of articles by Stephen Cleary. But, why Task.Run() works is a mystery.
Code snippet:
// This Create Method results in Deadlock
public async Task<string> Create(MyConfig config)
{
Document doc = await Client.CreateDocumentAsync(CollectionUri, config).ConfigureAwait(false);
return doc.Id;
}
// Uses Task.Run() which works properly, why??
public string Create(MyConfig config)
{
Document doc = Task.Run(() => Client.CreateDocumentAsync(CollectionUri, config)).Result;
return doc.Id;
}
[HttpPost]
public ActionResult CreateConfig(MyConfig config)
{
string id = Create(config).Result;
return Json(id);
}
I believe Lukazoid is correct. To put it another way...
// This Create Method results in Deadlock
public async Task<string> Create(MyConfig config)
{
Document doc = await Client.CreateDocumentAsync(CollectionUri, config).ConfigureAwait(false);
return doc.Id;
}
You can't just stick a ConfigureAwait(false)
at one level and have it magically prevent deadlocks. ConfigureAwait(false)
can only prevent deadlocks if it is used by every await
in the transitive closure of that method and all methods it calls.
In other words, ConfigureAwait(false)
needs to be used for every await
in Create
(which it is), and it also needs to be used for every await
in CreateDocumentAsync
(which we don't know), and it also needs to be used for every await
in every method that CreateDocumentAsync
calls, etc.
This is one reason why it is such a brittle "solution" to the deadlock problem.
In the first example, the implementation of Client.CreateDocumentAsync
is deadlocking because it is trying to execute a continuation using the current SynchronizationContext
.
When using Task.Run
, the delegate will be invoked on a ThreadPool thread, this means there will be no current SynchronizationContext
so all continuations will be resumed using a ThreadPool thread. This means it will not deadlock.
Out of interest, why is your CreateConfig
method not async? Most recent versions of MVC and WebAPI support asynchronous methods, getting rid of the .Result
would be the best solution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With