async/await 与 ConfigureAwait 的 continueOnCapturedContext 参数和用于异步延续的 SynchronizationContext



我想把代码放在第一位,然后解释情况,并在此基础上提出我的问题:

public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private async void Button_Click_2(object sender, RoutedEventArgs e) {
var result = await GetValuesAsync();
Foo.Text += result;
}
public async Task<string> GetValuesAsync() {           
using (var httpClient = new HttpClient()) {
var response = await httpClient
.GetAsync("http://www.google.com")
.ConfigureAwait(continueOnCapturedContext: false);

// This is the continuation for the httpClient.GetAsync method.
// We shouldn't get back to sync context here
// Cuz the continueOnCapturedContext is set to *false*
// for the Task which is returned from httpClient.GetAsync method
var html = await GetStringAsync();
// This is the continuation for the GetStringAsync method.
// Should I get back to sync context here?
// Cuz the continueOnCapturedContext is set to *true*
// for the Task which is returned from GetStringAsync 
// However, GetStringAsync may be executed in another thread
// which has no knowledge for the sync context 
// because the continueOnCapturedContext is set to *false*
// for the Task which is returned from httpClient.GetAsync method.
// But, on the other hand, GetStringAsync method also has a 
// chance to be executed in the UI thread but we shouldn't be
// relying on that. 
html += "Hey...";
Foo.Text = html;
return html;
}
}
public async Task<string> GetStringAsync() {
await Task.Delay(1000);
return "Done...";
}
}

这是一个相当简单的 WPF 示例,在 .NET 4.5 上运行,可能没有多大意义,但这应该有助于我解释我的情况。

我在屏幕上有一个按钮,它有一个异步点击事件。当您查看GetValuesAsync代码时,您将看到await关键字的用法两次。第一次使用时,我将Task.ConfigureAwait方法的参数设置为continueOnCapturedContextfalse。所以,这表明我不一定希望我的延续在SynchronizationContext.Current内执行。目前为止,一切都好。

在第二次await用法(使用GetStringAsync方法)时,我没有调用ConfigureAwait方法。因此,我基本上表示我想返回到当前的同步上下文以继续GetStringAsync方法。因此,如您所见,我尝试在延续中设置TextBlock.Text(属于 UI 线程)属性。

当我运行应用程序并单击按钮时,我收到一个异常,给我以下消息:

调用线程无法访问此对象,因为不同的 线程拥有它。

起初,这对我来说毫无意义,我认为我发现了一个错误,但后来,我意识到GetStringAsync可能会在另一个线程(很可能)中执行,该线程与 UI 线程不同,并且不知道同步上下文,因为continueOnCapturedContext设置为falsehttpClient.GetAsync方法返回的Task

这里的情况是这样吗?此外,在这种情况下,GetStringAsync方法是否有机会回发到 UI 线程 bacuse httpClient.GetAsync 方法延续可以在 UI 线程中执行?

我在代码中也有一些注释。鉴于我的问题和代码中的注释,我在这里遗漏了什么吗?

当你调用ConfigureAwait(false)时,方法的其余部分将在线程池线程上执行,除非awaitTask已经完成。

由于GetAsync几乎肯定会异步运行,因此我希望GetStringAsync线程池线程上运行。

public async Task<string> GetValuesAsync() {           
using (var httpClient = new HttpClient()) {
var response = await httpClient
.GetAsync("http://www.google.com")
.ConfigureAwait(continueOnCapturedContext: false);
// And now we're on the thread pool thread.
// This "await" will capture the current SynchronizationContext...
var html = await GetStringAsync();
// ... and resume it here.
// But it's not the UI SynchronizationContext.
// It's the ThreadPool SynchronizationContext.
// So we're back on a thread pool thread here.
// So this will raise an exception.
html += "Hey...";
Foo.Text = html;
return html;
}
}

另外,在这种情况下,GetStringAsync 方法是否有机会回发到 UI 线程 bacuse httpClient.GetAsync 方法延续可以在 UI 线程内执行?

GetStringAsync在 UI 线程上运行的唯一方法是GetAsync在实际await之前完成。

出于这个原因,我更喜欢在不再需要上下文时对每个await都使用ConfigureAwait(false)

最新更新