3

本文来自SynchronizationContext可能与以下内容有关的州ExecutionContext

private void button1_Click(object sender, EventArgs e)  { 
    button1.Text = await Task.Run(async delegate 
    { 
        string data = await DownloadAsync(); 
        return Compute(data); 
    });  
}

这是我的心理模型告诉我的,这段代码会发生什么。用户单击 button1,导致 UI 框架在 UI 线程上调用 button1_Click。然后代码启动一个工作项以在 ThreadPool 上运行(通过 Task.Run)。该工作项开始一些下载工作并异步等待它完成。ThreadPool 上的后续工作项然后对该下载结果执行一些计算密集型操作,并返回结果,从而导致 UI 线程上正在等待的任务完成。此时,UI 线程处理此 button1_Click 方法的剩余部分,将计算结果存储到 button1 的 Text 属性中。

如果 SynchronizationContext 不作为 ExecutionContext 的一部分流动,我的期望是有效的。但是,如果它确实流动,我将非常失望。Task.Run 在调用时捕获 ExecutionContext,并使用它来运行传递给它的委托。这意味着在调用 Task.Run 时处于当前状态的 UI SynchronizationContext 将流入任务,并且在调用 DownloadAsync 并等待生成的任务时处于当前状态。这意味着 await 将看到 Current SynchronizationContext 和 Post 异步方法的其余部分作为在 UI 线程上运行的延续。这意味着我的 Compute 方法很可能会在 UI 线程上运行,而不是在 ThreadPool 上运行,从而导致我的应用出现响应问题。

故事现在变得有点混乱:ExecutionContext 实际上有两个 Capture 方法,但其中只有一个是公共的。内部的(mscorlib 内部的)是从 mscorlib 公开的大多数异步功能所使用的,它可以选择允许调用者禁止捕获 SynchronizationContext 作为 ExecutionContext 的一部分;与此相对应,还有一个 Run 方法的内部重载,它支持忽略存储在 ExecutionContext 中的 SynchronizationContext,实际上假装没有被捕获(这也是 mscorlib 中大多数功能使用的重载)。这意味着几乎任何其核心实现驻留在 mscorlib 中的异步操作都不会将 SynchronizationContext 作为 ExecutionContext 的一部分流动,但是任何其核心实现驻留在其他任何地方的异步操作都会将 SynchronizationContext 作为 ExecutionContext 的一部分流动。我之前提到,异步方法的“构建器”是负责在异步方法中流动 ExecutionContext 的类型,这些构建器确实存在于 mscorlib 中,并且它们确实使用内部重载……因此,SynchronizationContext 不会作为 ExecutionContext 的一部分跨等待(这与任务等待者如何支持捕获 SynchronizationContext 并回传给它是分开的)。为了帮助处理 ExecutionContext 确实流动 SynchronizationContext 的情况,异步方法基础结构尝试忽略由于正在流动而设置为 Current 的 SynchronizationContexts。

但是,我不清楚何时会发生这种情况。似乎在使用公共ExecutionContext.Capture方法并且不使用抑制流动的内部重载Task.RunSynchronizationContext发生这种情况,但我不知道那会是什么时候。ExecutionContext

在我对 .NET 4.5 的测试中,Task.Run似乎并没有SynchronizationContext使用ExecutionContext

private async void button1_Click(object sender, EventArgs e) {
    Console.WriteLine("Click context:" + SynchronizationContext.Current);
    button1.Text = await Task.Run(async delegate {

        // In my tests this always returns false
        Console.WriteLine("SynchronizationContext was flowed: " + (SynchronizationContext.Current != null));

        string data = await DownloadAsync();
        return Compute(data);
    });
}

所以我的问题是在什么情况下会Compute()在文章中讨论的 UI 上下文(阻塞 UI 线程)上运行?

4

1 回答 1

8

Task.Run 何时使用 ExecutionContext 流动 SynchronizationContext?

绝不。

那篇文章的重点是(公共 API 的)流动ExecutionContext将流动SynchronizationContext。但是Task.Run(并且“几乎所有核心​​实现驻留在 mscorlib 中的异步操作”)永远不会这样做。

以“我的期望是有效的”开头的段落是假设的。他正在描述如果使用公共 API 进行流动发生什么。如果这样做导致问题。这就是为什么它从不这样做。Task.RunExecutionContext

于 2013-10-23T17:56:52.563 回答