问题
Apparently i did not understand async/await
yet and the following basic example already causes some headache:
For test purposes i create a Window which represents my UI for now and i want to fire an asynchronous method which does some work in the background when the window opens. I added a Listview
to my window to test if the UI is responding.
When i execute the following code, two things happen that i do not understand:
- My
ListView
shows theCustomObjects
that i define in the constructor. Since i could not use the await keyword in my constructor, I was expecting that the call ofGetWebResponseAsync().ConfigureAwait(false)
would block my UI-Thread and produce a deadlock (like replacing the call withGetWebResponseAsync().Wait()
is doing). It seems likeConfigureAwait(false)
already makes my method run on another thread even though i did not run it as a task or await it? - The UI shows up, but freezes while the
Async
method is running. This is actually not unexpected but confuses me if i consider the previous observation that the Listview is apparently accessible from the code while i perform my async method. Doesn't that mean that my UI-Thread is NOT blocked and thus I should be able to interact with the UI?
Since i got stuck here, i would also like to know how i can properly call my async method in the constructor right away. If i use await GetWebResponseAsync().ConfigureAwait(false);
it doesn't compile since my constructor does not have the async
keyword. However i know by now that i should not use async void
(and even if i try to make my constructor an async void
method, it doesn't compile because member names can not be the same as their enclosing type
).
public partial class TitleWindow : Window
{
public TitleWindow()
{
InitializeComponent();
// I can not use this because the constructor is not async.
//Task tempTask = await GetWebResponseAsync().ConfigureAwait(false);
GetWebResponseAsync().ConfigureAwait(false);
//This should in theory test if my UI-Thread is blocked?!
List<CustomObject> items = new List<CustomObject>();
items.Add(new CustomObject() { Title = "CustomTitle", Year = 2100});
items.Add(new CustomObject() { Title = "CustomTitle2", Year = 2015});
lvTitles.ItemsSource = items;
}
public async Task GetWebResponseAsync(){
WebRequest request = WebRequest.Create("http://www.google.com");
request.Credentials = CredentialCache.DefaultCredentials;
WebResponse response = await request.GetResponseAsync();
//Output for test purposes.
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
string responseFromServer = await reader.ReadToEndAsync();
Console.WriteLine(responseFromServer);
return;
}
}
Update:
Yuval Itzchakovs Answer works perfectly fine for me.
Also this pattern (taken from the link in Yuval Itzchakovs answer) or a combination of both seems like the way to go in a more complex scenario. This provides a very comfortable possibility to ensure later on that the async code from the constructor is already completed by awaiting my Initialization Property.
public partial class TitleWindow : Window, IAsyncInitialization
{
public Task Initialization{get; private set;}
public TitleWindow()
{
InitializeComponent();
Initialization = GetWebResponseAsync();
}
public async Task GetWebResponseAsync(){
//unchanged
}
回答1:
It seems like ConfigureAwait(false) already makes my method run on another thread even though i did not run it as a task or await it?
That's incorrect. The method will run synchronously until hitting the first await
keyword. After that, since you're not using ConfigureAwait(false)
in GetWebResponseAsync
, it will marshal it's continuation again back onto the UI thread, which is probably why you're seeing you UI get stuck.
Doesn't that mean that my UI-Thread is NOT blocked and thus I should be able to interact with the UI?
Again, no. The async method isn't running on a background thread, it's consumed on the UI thread. When the method runs its synchronous part (such as Stream dataStream = response.GetResponseStream();
) it's still executing on the UI thread.
Calling async method from a constructor doesn't naturally work because constructors aren't async (Stephan Cleary has a good blog post about that).
What you can do is use is attach to an event which fires after the window is loaded, such as the Loaded
event, which you can execute async methods inside properly:
this.Loaded += OnWindowLoaded;
And then you can properly await
:
private async void OnWindowLoaded(object sender, RoutedEventArgs e)
{
await GetWebResponseAsync().ConfigureAwait(false);
}
Note that if there's no need for the synchronization context inside GetWebResponseAsync
, you can also use ConfigureAwait(false)
and save yourself the overhead of the sync context marshaling.
来源:https://stackoverflow.com/questions/28606556/async-await-am-i-using-the-wrong-synchronisation-context