async / await with 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 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 GetStringAsync() { await Task.Delay(1000); return "Done..."; } }
这是一个相当简单的WPF示例,它运行在.NET 4.5上,可能没有多大意义,但这可以帮助我解释我的情况。
我在屏幕上有一个按钮,它有一个异步点击事件。 当您查看GetValuesAsync
代码时,您将看到两次await
关键字的用法。 在第一次使用时,我将Task.ConfigureAwait
方法的continueOnCapturedContext
参数设置为false
。 所以,这表明我不一定希望继续在SynchronizationContext.Current
执行。 到现在为止还挺好。
在第二次await
使用(使用GetStringAsync
方法),我没有调用ConfigureAwait
方法。 所以,我基本上表示我想回到当前的同步上下文来继续GetStringAsync
方法。 因此,正如您所看到的,我尝试在continuation中设置TextBlock.Text
(属于UI线程)属性。
当我运行应用程序并单击按钮时,我得到一个exception,给我以下消息:
调用线程无法访问此对象,因为另一个线程拥有它。
起初,这对我没有任何意义,我认为我发现了一个错误,但后来,我意识到GetStringAsync
可能在另一个线程(很有可能)中执行,该线程与UI线程不同,并且不知道同步上下文,因为对于从httpClient.GetAsync
方法返回的Task
, continueOnCapturedContext
设置为false
。
这是这种情况吗? 此外,在这种情况下,是否有机会将GetStringAsync
方法发布回UI线程,可以在UI线程内执行httpClient.GetAsync方法延续?
我在代码中也有一些注释。 鉴于我的问题和代码中的注释,我在这里遗漏了什么吗?
当您调用ConfigureAwait(false)
, 除了您正在await
的Task
已经完成之外,该方法的其余部分将在线程池线程上执行。
由于GetAsync
几乎肯定会异步运行,我希望GetStringAsync
能够在线程池线程上运行。
public async Task 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线程,可以在UI线程内执行httpClient.GetAsync方法延续?
GetStringAsync
将在UI线程上运行的唯一方法是GetAsync
在实际await
ed之前完成。 极不可能。
出于这个原因,一旦不再需要上下文,我更喜欢在每次 await
使用ConfigureAwait(false)
。