【问题标题】:async/await with ConfigureAwait's continueOnCapturedContext parameter and SynchronizationContext for asynchronous continuationsasync/await 与 ConfigureAwait 的 continueOnCapturedContext 参数和用于异步延续的 SynchronizationContext
【发布时间】:2012-10-17 11:16:27
【问题描述】:

我想先放代码,然后解释情况并据此提出我的问题:

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方法的continueOnCapturedContext参数设置为false。因此,这表明我不一定希望SynchronizationContext.Current 中执行我的延续。到目前为止一切顺利。

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

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

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

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

这里是这样吗?另外,在这种情况下,GetStringAsync 方法是否有机会回传到 UI 线程,因为 httpClient.GetAsync 方法延续可能在 UI 线程内执行?

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

【问题讨论】:

标签: c# asynchronous task-parallel-library async-await synchronizationcontext


【解决方案1】:

当您调用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 线程,因为 httpClient.GetAsync 方法的延续可能在 UI 线程内执行?

GetStringAsync 在 UI 线程上运行的唯一方法是,如果 GetAsync 在它实际上是 awaited 之前完成。 不太可能。

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

【讨论】:

  • 谢谢!你的最后一句话是我考虑遵循的方式(我一直是,但现在我要坚持更多)。这不会产生明显的不同,但使用ConfigureAwait(false) 也可以让我们免于不必要的SynchronizationContext 检查是否不需要。
  • “极不可能”当然是不够的。要么它必须是 100%,要么你的代码需要容忍这两种情况。
  • 我大体上同意。这就是为什么我更喜欢在不再需要上下文时为每个await 使用ConfigureAwait(false)
  • @LawrenceA.Contreras:您应该为每种方法分别决定是否应该使用ConfigureAwait(false)。我总是构造我的代码,以便方法总是使用ConfigureAwait(false),或者它从不使用ConfigureAwait(false)
  • @LawrenceA.Contreras:是的。
猜你喜欢
  • 2015-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-10
  • 2012-12-17
  • 2013-04-11
  • 1970-01-01
相关资源
最近更新 更多