“传播ExecutionContext信息”,无论这是什么意思。
实际上没有关于这个的好的详尽定义,我肯定不会尝试提供一个,因为我知道我会漏掉一些重要的内容。不过,我确实知道,流动ExecutionContext对于安全原因是必要的——这就是为什么所有不流动上下文的方法都使用Unsafe命名约定。
Stephen Toub在他的博客中提到:
“ExecutionContext涉及“环境”信息,也就是说,它存储与运行当前环境或“上下文”相关的数据……ExecutionContext包含的上下文之一是SecurityContext,它保留诸如当前“principal”以及代码访问安全性(CAS)拒绝和许可等信息。”
所以首先要认识到的是,ExecutionContext必须被传递。另外,还有一个问题,
Stephen Toub在他的博客中描述了这个问题。为了历史背景,这个描述是在
async
/
await
技术还是预发布阶段的时候(但是可以公开使用):
“许多人没有意识到他们的awaiter需要传播ExecutionContext,以确保上下文在await点之间流动……因此,我们修改了Framework中的async方法构建器(例如AsyncTaskMethodBuilder)……这些构建器现在自己在await点之间传递ExecutionContext,从awaiter身上解除了这个责任。”
最初,awaiter会传播ExecutionContext,但在官方
async
/
await
发布之前进行了更改,所以构建器会传播ExecutionContext。当然,这意味着awaiter不再需要传播ExecutionContext;如果他们这样做了,异步代码将会在两个地方传播它(其中“flow”是一个“capture”,后跟一个“在这个捕获的上下文中执行委托”)。
现在有足够的信息来回答以下问题:
“为什么awaiter要实现ICriticalNotifyCompletion?TaskAwaiter实现ICriticalNotifyCompletion的影响是什么?如果没有实现会怎样?”
如果awaiter没有实现ICriticalNotifyCompletion,则使用该代码的await将导致ExecutionContext被传递两次(一次由awaiter,一次由async方法构建器)。这并不会破坏任何东西,只是效率不如可能的高。
“这样实现ICriticalNotifyCompletion是否使OnCompleted(Action)变得多余?”
不完全正确。再次参考
Stephen Toub的文章:
如果您正在构建一个带有AllowPartiallyTrustedCallersAttribute (APTCA)属性的程序集,则需要确保从您的程序集公开的任何API在异步点之间正确地流经ExecutionContext...否则可能存在安全漏洞。由于awaiter类型通常是在APTCA程序集中实现的,而OnCompleted可能会被用户直接调用(尽管它实际上是要被编译器使用的),因此OnCompleted需要流经ExecutionContext...我们还有UnsafeOnCompleted,它不需要流经ExecutionContext,但也标记为SecurityCritical,使得部分信任的代码无法调用它。
结论是:
如果您正在实现自己的awaiter,请尽可能同时实现INotifyCompletion和ICriticalNotifyCompletion,在前者中流经ExecutionContext,在后者中不流经。唯一不实现两者的好理由是:当您在无法流经ExecutionContext的情况下实现awaiter时,例如部分受信任的awaiter或您没有使用ExecutionContext的能力,或者您的awaiter依赖的API没有任何选项可以控制是否流经上下文...在这种情况下,只需实现INotifyCompletion即可。
我只稍微修改了这个结论。自上述文章发布以来,已经过去了将近十年,我认为很少使用APTCA程序集。即.NET Core根本不支持部分信任。对于.NET Core,我认为您可以说OnCompleted
是多余的。然而,在.NET Framework世界中,这种区别仍然很重要,因为OnCompleted
对于部分受信任程序集中的awaiter是必需的。
所以我的建议是:在实现awaiter时,如果可以不流动ExecutionContext
,请始终实现ICriticalNotifyCompletion
。否则,只需保留常规的OnCompleted
实现。