问题 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<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 关键字两次。第一次使用,我设置 continueOnCapturedContext 的参数 Task.ConfigureAwait 方法 false。所以,这表明我 不一定要 我继续在里面执行 SynchronizationContext.Current。到现在为止还挺好。

在第二个 await 用法(用 GetStringAsync 方法),我没有打电话给 ConfigureAwait 方法。所以,我基本上表明了我  回到当前的同步上下文继续 GetStringAsync 方法。所以,正如你所看到的,我试着设置 TextBlock.Text (继承者内部属于UI线程)属性。

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

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

起初,这对我没有任何意义,我认为我发现了一个错误,但后来,我意识到了这一点 GetStringAsync 可能在另一个线程(很有可能)中执行,该线程与UI线程不同,并且不知道同步上下文,因为 continueOnCapturedContext 被设定为 false 为了 Task 从...返回 httpClient.GetAsync 方法。

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

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


1630
2017-10-17 11:16


起源

这个答案有帮助吗? stackoverflow.com/a/12357113/171121 - Dax Fohl


答案:


你打电话时 ConfigureAwait(false),该方法的其余部分将在线程池线程上执行 除非 该 Task 你是 awaiting已经完成了。

以来 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线程,可以在UI线程内执行httpClient.GetAsync方法延续?

唯一的办法 GetStringAsync 将在UI线程上运行if GetAsync 在它实际完成之前完成 await编辑。 高度 不太可能。

出于这个原因,我更喜欢使用 ConfigureAwait(false) 对于 一切  await 一旦不再需要上下文。


11
2017-10-17 11:44



谢谢!你的最后一句是我考虑跟随的方式(我已经去过,但现在我要坚持更多)。这不会产生显着的差异,而是使用 ConfigureAwait(false)也将使我们免于不必要的 SynchronizationContext 检查是否不需要。 - tugberk
当然,“极不可能”是不够的。它必须是100%,否则您的代码需要容忍这两种情况。 - usr
我一般同意。这就是我喜欢使用的原因 ConfigureAwait(false) 为每一个人 await 一旦不再需要上下文。 - Stephen Cleary
@ LawrenceA.Contreras:您应该分别决定是否应该使用每种方法 ConfigureAwait(false)。我总是构造我的代码,以便任何一种方法 总是 使用 ConfigureAwait(false) 或它 决不 使用 ConfigureAwait(false)。 - Stephen Cleary
@ LawrenceA.Contreras:是的。 - Stephen Cleary