ICriticalNotifyCompletion 有什么用?

Tra*_*Man 11 c# async-await

我试图了解 C# 异步机制实际上是如何工作的,并且ICriticalNotifyCompletion接口是一个混乱的来源。

该接口提供了两种方法:OnCompleted(Action)、继承自INotifyCompletionUnsafeOnCompleted(Action)。该文件有两种方法完全相同的描述:“时间表是这样的情况下完成时调用的延续动作。” .

唯一的区别是关于UnsafeOnCompleted(Action)声明它不必 “传播 ExecutionContext 信息”的评论,无论这意味着什么。没有明确规定OnCompleted(Action)必须“传播 ExecutionContext 信息”

关于可等待表达式的文档指出,UnsafeOnCompleted(Action)如果等待者实现ICriticalNotifyCompletion. 所以实施ICriticalNotifyCompletionOnCompleted(Action)多余的吗?

为什么等待者会实现ICriticalNotifyCompletionTaskAwaiter实施的意义是ICriticalNotifyCompletion什么?如果没有呢?

Ste*_*ary 5

“传播 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实现即可。