我试图了解 C# 异步机制实际上是如何工作的,并且ICriticalNotifyCompletion
接口是一个混乱的来源。
该接口提供了两种方法:OnCompleted(Action)
、继承自INotifyCompletion
和UnsafeOnCompleted(Action)
。该文件有两种方法完全相同的描述:“时间表是这样的情况下完成时调用的延续动作。” .
唯一的区别是关于UnsafeOnCompleted(Action)
声明它不必 “传播 ExecutionContext 信息”的评论,无论这意味着什么。没有明确规定OnCompleted(Action)
必须“传播 ExecutionContext 信息”。
关于可等待表达式的文档指出,UnsafeOnCompleted(Action)
如果等待者实现ICriticalNotifyCompletion
. 所以实施ICriticalNotifyCompletion
是OnCompleted(Action)
多余的吗?
为什么等待者会实现ICriticalNotifyCompletion
?TaskAwaiter
实施的意义是ICriticalNotifyCompletion
什么?如果没有呢?
“传播 ExecutionContext 信息”,无论这意味着什么
实际上,对此没有很好的详尽定义,我当然不会尝试提供一个,因为我知道我会错过一些重要的东西。不过,我确实知道,出于安全原因,流动ExecutionContext
是必要的——这就是为什么所有不流动上下文的方法都使用Unsafe
命名约定的原因。Stephen Toub 有这样的说法:
ExecutionContext 是关于“环境”信息的,这意味着它存储与当前环境或您正在运行的“上下文”相关的数据...... ExecutionContext 包含的上下文之一是 SecurityContext,它维护像当前“主体”这样的信息”和有关代码访问安全 (CAS) 的信息拒绝和允许。
所以首先要认识到的是ExecutionContext
必须是流动的。Stephen Toub再次描述了下一块拼图。对于历史背景,此描述是async
/await
仍然是预发布(但公开可用)技术的时候:
许多人没有意识到他们的等待者需要流动 ExecutionContext 以确保上下文流过等待点...... [所以]我们修改了框架中的异步方法构建器(例如 AsyncTaskMethodBuilder)......现在构建器自己在等待点之间流动 ExecutionContext,从而将责任从等待者身上移开。
最初,等待者会流动ExecutionContext
,但在正式async
/await
发布之前改变了这一点,以便建设者流动ExecutionContext
。自然,这意味着等待者不再需要流动ExecutionContext
;如果他们这样做了,异步代码最终会流过两次(其中“流”是“捕获”,然后是“在此捕获的上下文中执行委托”)。
现在有足够的信息来回答这些问题:
为什么等待者要实现 ICriticalNotifyCompletion?TaskAwaiter 实施 ICriticalNotifyCompletion 的含义是什么?如果没有呢?
如果等待者没有实现ICriticalNotifyCompletion
,那么await
使用该代码最终会流过ExecutionContext
两次(等待者一次,async
方法构建者一次)。它不会破坏任何东西;它只会比它可能的效率低。
那么实施 ICriticalNotifyCompletion 会使 OnCompleted(Action) 变得多余吗?
不完全的。再次委托给 Stephen Toub:
如果您正在构建一个应用了 AllowPartiallyTrustedCallersAttribute (APTCA) 的程序集,您需要确保程序集中的任何公开公开的 API 正确地跨异步点流动 ExecutionContext ……如果不这样做,则可能是一个很大的安全漏洞。由于 awaiter 类型通常在 APTCA 程序集中实现,并且由于 OnCompleted 可以由用户直接调用(即使它真的打算由编译器使用),OnCompleted 需要流 ExecutionContext...我们还有 UnsafeOnCompleted,它没有不需要流 ExecutionContext,但它也被标记为 SecurityCritical,这样部分受信任的代码就无法调用它。
结论是:
如果您正在实现自己的等待程序,请尽可能同时实现 INotifyCompletion 和 ICriticalNotifyCompletion,在前者中流动 ExecutionContext 而在后者中不流动。不同时实现两者的唯一好理由是,如果您在无法流动 ExecutionContext 的情况下实现等待者,例如,部分受信任的等待者或您无法使用 ExecutionContext 的情况,或者您的等待程序所依赖的 API 并没有给您任何关于是否流上下文的选择……在这种情况下,您可以只实现 INotifyCompletion。
我只会稍微修改这个结论。在编写上述内容后的近十年中,我想说使用 APTCA 程序集并不常见。即,.NET Core 根本不支持部分信任。对于 .NET Core,我相信你可以说这OnCompleted
是多余的。但是,这种区别在 .NET Framework 世界中仍然很重要,这OnCompleted
对于部分信任程序集中的等待程序来说是必要的。
所以我会说:当实现一个等待者时,ICriticalNotifyCompletion
如果你能实现它,总是实现它(即,没有流动ExecutionContext
)。否则,只需保持常规OnCompleted
实现即可。
归档时间: |
|
查看次数: |
346 次 |
最近记录: |