async/await - 我使用了错误的同步上下文吗?
async/await - am I using the wrong synchronisation context?
显然我还没有理解 async/await
,下面的基本示例已经让人头疼了:
出于测试目的,我创建了一个 Window,它现在代表我的 UI,我想触发一个异步方法,它在 window 打开时在后台执行一些操作。我在我的 window 中添加了一个 Listview
来测试 UI 是否有响应。
当我执行下面的代码时,发生了两件我不明白的事情:
- 我的
ListView
显示了我在构造函数中定义的 CustomObjects
。因为我不能在我的构造函数中使用 await 关键字,所以我期望 GetWebResponseAsync().ConfigureAwait(false)
的调用会阻塞我的 UI-Thread 并产生死锁(比如用 GetWebResponseAsync().Wait()
替换调用是正在做)。似乎 ConfigureAwait(false)
已经在另一个线程上使我的方法 运行 即使我没有 运行 它作为任务或等待它?
- UI 显示,但在
Async
方法 运行ning 时冻结。这实际上并不意外,但如果我考虑之前的观察结果,即在我执行异步方法时显然可以从代码访问 Listview,这会让我感到困惑。这是否意味着我的 UI-Thread 未被阻止,因此我应该能够与 UI 交互?
由于我被困在这里,我也想知道如何立即在构造函数中正确调用我的异步方法。如果我使用 await GetWebResponseAsync().ConfigureAwait(false);
它不会编译,因为我的构造函数没有 async
关键字。但是我现在知道我不应该使用 async void
(即使我试图让我的构造函数成为 async void
方法,它也不会编译,因为 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;
}
}
更新:
Yuval Itzchakovs 的回答对我来说非常好。
此外,这种模式(取自 Yuval Itzchakovs 回答中的 link)或两者的组合似乎是在更复杂的场景中采用的方式。这提供了一种非常舒适的可能性,可以确保稍后通过等待我的初始化 属性 来确保来自构造函数的异步代码已经完成。
public partial class TitleWindow : Window, IAsyncInitialization
{
public Task Initialization{get; private set;}
public TitleWindow()
{
InitializeComponent();
Initialization = GetWebResponseAsync();
}
public async Task GetWebResponseAsync(){
//unchanged
}
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?
这是不正确的。该方法将 运行 同步,直到命中第一个 await
关键字。在那之后,因为你没有在 GetWebResponseAsync
中使用 ConfigureAwait(false)
,它会再次将它的延续编组回 UI 线程,这可能就是你看到 [=37= 的原因]卡住了。
Doesn't that mean that my UI-Thread is NOT blocked and thus I should
be able to interact with the UI?
同样,不。异步方法不是在后台线程上 运行ning,而是在 UI 线程上使用。当方法 运行 是其同步部分(例如 Stream dataStream = response.GetResponseStream();
)时,它仍在 UI 线程上执行。
从构造函数调用异步方法自然不会起作用,因为构造函数不是异步的(Stephan Cleary 对此有很好的 blog post)。
你 可以 做的是附加到一个事件上,该事件在 window 加载后触发,例如 Loaded
事件,你可以正确执行里面的异步方法:
this.Loaded += OnWindowLoaded;
然后就可以正常await
:
private async void OnWindowLoaded(object sender, RoutedEventArgs e)
{
await GetWebResponseAsync().ConfigureAwait(false);
}
请注意,如果 GetWebResponseAsync
中不需要同步上下文,您也可以使用 ConfigureAwait(false)
并节省同步上下文编组的开销。
显然我还没有理解 async/await
,下面的基本示例已经让人头疼了:
出于测试目的,我创建了一个 Window,它现在代表我的 UI,我想触发一个异步方法,它在 window 打开时在后台执行一些操作。我在我的 window 中添加了一个 Listview
来测试 UI 是否有响应。
当我执行下面的代码时,发生了两件我不明白的事情:
- 我的
ListView
显示了我在构造函数中定义的CustomObjects
。因为我不能在我的构造函数中使用 await 关键字,所以我期望GetWebResponseAsync().ConfigureAwait(false)
的调用会阻塞我的 UI-Thread 并产生死锁(比如用GetWebResponseAsync().Wait()
替换调用是正在做)。似乎ConfigureAwait(false)
已经在另一个线程上使我的方法 运行 即使我没有 运行 它作为任务或等待它? - UI 显示,但在
Async
方法 运行ning 时冻结。这实际上并不意外,但如果我考虑之前的观察结果,即在我执行异步方法时显然可以从代码访问 Listview,这会让我感到困惑。这是否意味着我的 UI-Thread 未被阻止,因此我应该能够与 UI 交互?
由于我被困在这里,我也想知道如何立即在构造函数中正确调用我的异步方法。如果我使用 await GetWebResponseAsync().ConfigureAwait(false);
它不会编译,因为我的构造函数没有 async
关键字。但是我现在知道我不应该使用 async void
(即使我试图让我的构造函数成为 async void
方法,它也不会编译,因为 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;
}
}
更新:
Yuval Itzchakovs 的回答对我来说非常好。
此外,这种模式(取自 Yuval Itzchakovs 回答中的 link)或两者的组合似乎是在更复杂的场景中采用的方式。这提供了一种非常舒适的可能性,可以确保稍后通过等待我的初始化 属性 来确保来自构造函数的异步代码已经完成。
public partial class TitleWindow : Window, IAsyncInitialization
{
public Task Initialization{get; private set;}
public TitleWindow()
{
InitializeComponent();
Initialization = GetWebResponseAsync();
}
public async Task GetWebResponseAsync(){
//unchanged
}
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?
这是不正确的。该方法将 运行 同步,直到命中第一个 await
关键字。在那之后,因为你没有在 GetWebResponseAsync
中使用 ConfigureAwait(false)
,它会再次将它的延续编组回 UI 线程,这可能就是你看到 [=37= 的原因]卡住了。
Doesn't that mean that my UI-Thread is NOT blocked and thus I should be able to interact with the UI?
同样,不。异步方法不是在后台线程上 运行ning,而是在 UI 线程上使用。当方法 运行 是其同步部分(例如 Stream dataStream = response.GetResponseStream();
)时,它仍在 UI 线程上执行。
从构造函数调用异步方法自然不会起作用,因为构造函数不是异步的(Stephan Cleary 对此有很好的 blog post)。
你 可以 做的是附加到一个事件上,该事件在 window 加载后触发,例如 Loaded
事件,你可以正确执行里面的异步方法:
this.Loaded += OnWindowLoaded;
然后就可以正常await
:
private async void OnWindowLoaded(object sender, RoutedEventArgs e)
{
await GetWebResponseAsync().ConfigureAwait(false);
}
请注意,如果 GetWebResponseAsync
中不需要同步上下文,您也可以使用 ConfigureAwait(false)
并节省同步上下文编组的开销。