且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

为什么是“等待 Task.Yield()"?Thread.CurrentPrincipal 需要正确流动吗?

更新时间:2023-12-03 19:08:10

多有趣!看起来 Thread.CurrentPrincipal 是基于 logical 调用上下文,而不是每个线程的调用上下文.IMO 这很不直观,我很想知道为什么以这种方式实施.

How interesting! It appears that Thread.CurrentPrincipal is based on the logical call context, not the per-thread call context. IMO this is quite unintuitive and I'd be curious to hear why it was implemented this way.

在 .NET 4.5. 中,async 方法与逻辑调用上下文交互,以便它更适合与 async 方法一起流动.我有一篇关于该主题的博客文章;AFAIK 这是唯一记录它的地方.在 .NET 4.5 中,在每个 async 方法的开头,它会为其逻辑调用上下文激活写入时复制"行为.当(如果)逻辑调用上下文被修改时,它会首先创建自己的本地副本.

In .NET 4.5., async methods interact with the logical call context so that it will more properly flow with async methods. I have a blog post on the topic; AFAIK that's the only place where it's documented. In .NET 4.5, at the beginning of every async method, it activates a "copy-on-write" behavior for its logical call context. When (if) the logical call context is modified, it will create a local copy of itself first.

您可以通过观察窗口中的System.Threading.Thread.CurrentThread.ExecutionContextBelongsToCurrentScope 来查看逻辑调用上下文的局部性"(即是否已复制).

You can see the "localness" of the logical call context (i.e., whether it has been copied) by observing System.Threading.Thread.CurrentThread.ExecutionContextBelongsToCurrentScope in a watch window.

如果您没有 Yield,那么当您设置 Thread.CurrentPrincipal 时,您正在创建逻辑调用上下文的副本,该副本被视为本地" 到 async 方法.当 async 方法返回时,该本地上下文被丢弃,原始上下文取而代之(您可以看到 ExecutionContextBelongsToCurrentScope 返回到 false).

If you don't Yield, then when you set Thread.CurrentPrincipal, you're creating a copy of the logical call context, which is treated as "local" to that async method. When the async method returns, that local context is discarded and the original context takes its place (you can see ExecutionContextBelongsToCurrentScope returning to false).

另一方面,如果您执行 Yield,则 SynchronizationContext 行为将接管.实际发生的是 HttpContext 被捕获并用于恢复这两种方法.在这种情况下,您不会看到 Thread.CurrentPrincipalAuthenticateAsync 保存到 GetAsync;实际发生的是 HttpContext 被保留,然后 HttpContext.用户在方法恢复之前覆盖了Thread.CurrentPrincipal.

On the other hand, if you do Yield, then the SynchronizationContext behavior takes over. What actually happens is that the HttpContext is captured and used to resume both methods. In this case, you're not seeing Thread.CurrentPrincipal preserved from AuthenticateAsync to GetAsync; what is actually happening is HttpContext is preserved, and then HttpContext.User is overwriting Thread.CurrentPrincipal before the methods resume.

如果您将 Yield 移动到 GetAsync,您会看到类似的行为:Thread.CurrentPrincipal 被视为范围为 AuthenticateAsync;当该方法返回时,它会恢复其值.但是,HttpContext.User 仍然设置正确,该值将被 Yield 捕获,当方法恢复时,它将覆盖 Thread.CurrentPrincipal.

If you move the Yield into GetAsync, you see similar behavior: Thread.CurrentPrincipal is treated as a local modification scoped to AuthenticateAsync; it reverts its value when that method returns. However, HttpContext.User is still set correctly, and that value will be captured by Yield and when the method resumes, it will overwrite Thread.CurrentPrincipal.