This question already has an answer here:
So I'm writing an application in which I want to expose a series of methods with both synchronous and asynchronous equivalents. To do this, I figured the easiest approach was to write the logic in the asnyc method, and write synchronous methods as wrappers around the async methods, waiting synchronously for them to deliver their results. The code isn't playing ball. In the following code sample (not my real code but a reduction of the basic problem), the line Console.WriteLine(result)
is never reached - the preceding line hangs forever. Curiously though, if I copy this pattern more or less verbatim into a Console app, it works.
What am I doing wrong? Is this simply a bad pattern, and if so, what pattern should I be using instead?
public partial class MainWindow : Window {
public MainWindow() {
this.InitializeComponent();
var result = MyMethod(); //Never returns
Console.WriteLine(result);
}
public string MyMethod() {
return MyMethodAsync().Result; //Hangs here
}
public async Task<string> MyMethodAsync() { //Imagine the logic here is more complex
using (var cl = new HttpClient()) {
return await cl.GetStringAsync("http://www.google.co.uk/");
}
}
}
This is a classic deadlock. The UI is waiting on the async method to finish, but the async method trys to update the UI thread and BOOM, deadlock.
That's because your WinForm application has a custom
SynchronizationContext
. It is caputred implicitly and its job is to marshal work back onto the UI thread once returning from yourawait
.Should you really expose synchronous wrappers around asynchronous operations?, the answer is no.
There is a way out of it, but i dont really like it. If you absolutely have to (you don't) call your code synchronously (again, you really shouldn't), use
ConfigureAwait(false)
inside the async method. This instructs theawaitable
not to capture the current synccontext, so it wont marshal work back onto the UI thread:Note that if you do this and then try to call any UI element afterwards, you'll end up with an
InvalidOperationException
since you won't be on the UI thread.Initializing the UI via a constructor is a common pattern. Stephan Cleary has a very nice series on async which you can find here.
Yes, absolutely. If you want to expose both asynchronous and synchronous APIs, use the proper api's which wouldn't get you into this situation (a deadlock) in the firstcase. For example, if you want to expose a synchronous
DownloadString
, useWebClient
instead.This is a common mistake.
MyMethodAsync
captures the current synchronization context, and tries to resume on the synchronization context (i.e. on the UI thread) after theawait
. But the UI thread is blocked becauseMyMethod
is synchronously waiting forMyMethodAsync
to complete, so you have a deadlock.You should usually not wait synchronously on the result of an async method. If you really have to, you can change
MyMethodAsync
so that it doesn't capture the synchronization context, usingConfigureAwait(false)
:Others have explained the deadlock situation (which I go into detail on my blog).
I'll address the other part of your question:
Yes, it is a bad pattern. Instead of exposing both synchronous and asynchronous APIs, let the operation itself determine whether it should be asynchronous or synchronous. E.g., CPU-bound code is generally synchronous, while I/O-bound code is generally asynchronous.
The proper pattern is to actually not expose a synchronous API for an HTTP operation:
Of course, then the question is how to initialize your UI. And the proper answer there is to synchronously initialize it into a "loading" state, and asynchronously update it to a "loaded" state. That way the UI thread is not blocked:
I have another blog post that addresses "asynchronous initialization" with a few various approaches.