标签:不包含 core 控制流 res iter 原因 oca 维护 post
注意: 本篇文章讲述的是在 .Net Framework 环境下的分析, 但是我相信这与 .Net Core 设计思想是一致,但在实现上一定优化了很多。
下面开始本次讲述:
ExecutionContext 实际上只是线程相关其他上下文的容器。
ExecutionContext 与周围环境的信息有关,这意味着,代码正在运行时,它存储了与 当前环境 或 “context” 有关的数据。
周围环境: 代码执行处,可以访问到的变量、方法、属性等等。
在同步世界:
[ThreadStatic]
字段或 ThreadLocal<T>
中。
在异步世界,TLS变得无关紧要,同步异步对比:
ExecutionContext 实际上是一个 state 包
ExecutionContext 是使用静态方法 Capture 捕获的:
// 周围环境的 state 捕获到 ec 中
ExecutionContext ec = ExecutionContext.Capture();
通过静态方法 Run ,在委托(Run方法的参数)调用时恢复 ExecutionContext
ExecutionContext.Run(ec, delegate
{
… // 这里的代码将上述 ec 的状态视为周围环境
}, null);
所有派生异步工作的方法都以这种方式捕获和还原 ExecutionContext 的。
例如:
Task.Run
时,对 Run
的调用将从调用线程中捕获 ExecutionContext ,并将该 ExecutionContext 实例存储到 Task
对象中Task.Run
的委托作为该 Task
执行的一部分被调用时,它是使用存储的 ExecutionContext 通过 ExecutionContext.Run
来完成的以下所有异步API的执行都是捕获 ExecutionContext 并将其存储,然后在调用某些代码时再使用存储的 ExecutionContext。
Task.Run
ThreadPool.QueueUserWorkItem
Delegate.BeginInvoke
Stream.BeginRead
DispatcherSynchronizationContext.Post
当我们谈论“flowing ExecutionContext”时,我们实际上是在讨论:
前面我们介绍了 SynchronizationContext
是如何调度线程的,现在,我们要进行进行一次对比:
SynchronizationContext.Post
只是使用捕获的状态来调用委托,而不是在调用委托时设置该状态为当前状态
async
和 await
关键字背后的框架支持会自动与 ExecutionContext 和 SynchronizationContext 交互。
每当代码等待一个可等待项(awaitable),该可等待项(awaitable) 的 等待者(awaiter) 说尚未完成时
IsCompleted
返回 false
则该方法需要暂停,并通过等待者(awaiter) 的 continuation
来恢复。
等待者(awaiter) : 可以理解为
await
产生的 Task对象。
await
的代码一直流到 continuation
委托的执行。
async
方法即将挂起时,基础设施将捕获 ExecutionContextExecutionContext
带领,启用重要的周围环境信息,去流过 awaits 。该框架还支持 SynchronizationContext 。前述对 ExecutionContext 的支持内置于表示 async
方法的“构建器”中
System.Runtime.CompilerServices.AsyncTaskMethodBuilder
await
/ async
会被编译成执行码并且这些构建器可确保 ExecutionContext 跨 await
点流动,无论使用哪种可等待项(awaitable)。
相反,对 SynchronizationContext 的支持内置在 awaiting
的且已经构建好的Task
和 Task<TResult>
中
自定义的等待者(awaiter) (比如 new Task(...)
)可以自己添加类似的逻辑,但是不会自动获得实例化时的SynchronizationContext
await
一个 task 时,默认情况下,等待者(awaiter) 将捕获当前的 SynchronizationContext(如果有的话)Post
这个前面提供的 continuation 委托并回到该 context 进行执行
ThreadPool
的线程上如果开发人员不希望这种封送处理行为,则可以通过更改在那里使用的 可等待项(awaitable) / 等待者(awaiter) 来控制它。
Task
或 Task<TResult>
就时采用上述方式await
方法 task.ConfigureAwait(…)
的返回值来修改这种封送处理行为
ConfigureAwait()
返回一个 可等待项(awaitable),它可以抑制此默认的封送处理行为。ConfigureAwait()
的唯一 bool
类型参数 continueOnCapturedContext
ConfigureAwait
如何,在恢复执行的线程上,运行时都会检查当前的 context ,以确定:
尽管 ConfigureAwait
提供了,用于改变 SynchronizationContext 行为的、显示的、与 await
相关的编程模型,但是没有用于抑制 ExecutionContext
流动的、与 await
相关的编程模型支持。
SynchronizationContext 不是 ExecutionContext 的一部分吗?
当您调用公共 ExecutionContext.Capture()
方法时,它将检查当前的 SynchronizationContext ,如果有,则将其存储到返回的 ExecutionContext 实例中。然后,当使用公共 ExecutionContext.Run(...)
方法时,在提供的委托执行期间,该捕获的 SynchronizationContext 被恢复为 Current 。
为什么这有问题?作为 ExecutionContext 的一部分而流动的 SynchronizationContext 更改了 SynchronizationContext.Current
的含义。
应该可以通过 SynchronizationContext.Current
返回到你最近调用 Current
时的环境
SynchronizationContext
流出,成为另一个线程的当前 SynchronizationContext
,则 SynchronizationContext.Current
就没有意义了,所以不是这样设计的。解释此问题的一个示例,代码如下:
private async void button1_Click(object sender, EventArgs e)
{
button1.Text = await Task.Run(async delegate
{
string data = await DownloadAsync();
return Compute(data);
});
}
Task.Run
);
Compute(data)
);Task
完成button1.Text
属性。如果 SynchronizationContext 不作为 ExecutionContext 的一部分流动,我的预期就是有根据的。
如果 SynchronizationContext 流动了,无论如何,我将感到非常失望。
假设:SynchronizationContext 作为 ExecutionContext 的一部分流动:
Task.Run
在调用时捕获 ExecutionContext ,并使用它运行传递给它委托。Task.Run
调用时的当前 SynchronizationContext 将流动到 Task
中,而且将在 DownloadAsync
执行和等待结果期间成为当前 SynchronizationContext ,
await
将看到当前 SynchronizationContext
,并 Post
异步方法的其余部分作为一个 continuation 返回到 UI线程 上运行。Compute
方法将在 UI线程 上运行,而不是在 ThreadPool 上运行,从而导致我的应用程序出现响应性问题。从实际结果来看这是不对的,假设执行的代码更像下面的
private async void button1_Click(object sender, EventArgs e)
{
string data = await DownloadAsync();
button1.Text = Compute(data);
}
实际: 现在,我们看看实际是如何处理的:
Task.Run(...)
这种异步Api的实现:
Capture
方法:
public
,供外部使用internal
的方法,是 mscorlib 大多数公开的异步功能(如:Task.Run(...)
)所使用的一个
Run
方法的 internal
重载也支持忽略存储在 ExecutionContext 中的 SynchronizationContext
这意味着:
标识 async
关键字方法的实现:
async
方法中流动 ExecutionContext 所使用的方式
internal
的重载做一些事情。Post
回来是分开的async
方法的基础设施尝试忽略由于流动而将 SynchronizationContexts 设置为 Current 。SynchronizationContext.Current
不会“流动”穿过 await
点。参考资料:
《ExecutionContext vs SynchronizationContext》 --- Stephen Toub
标签:不包含 core 控制流 res iter 原因 oca 维护 post
原文地址:https://www.cnblogs.com/BigBrotherStone/p/12316599.html