为什么是“等待 Task.Yield()"?Thread.CurrentPrincipal 需要正确流动吗? [英] Why is an "await Task.Yield()" required for Thread.CurrentPrincipal to flow correctly?

查看:29
本文介绍了为什么是“等待 Task.Yield()"?Thread.CurrentPrincipal 需要正确流动吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下代码已添加到新创建的 Visual Studio 2012 .NET 4.5 WebAPI 项目中.

The code below was added to a freshly created Visual Studio 2012 .NET 4.5 WebAPI project.

我正在尝试在异步方法中同时分配 HttpContext.Current.UserThread.CurrentPrincipal.除非 await Task.Yield();(或其他任何异步操作)被执行(将 true 传递到AuthenticateAsync() 将导致成功.

I'm trying to assign both HttpContext.Current.User and Thread.CurrentPrincipal in an asynchronous method. The assignment of Thread.CurrentPrincipal flows incorrectly unless an await Task.Yield(); (or anything else asynchronous) is executed (passing true to AuthenticateAsync() will result in success).

这是为什么?

using System.Security.Principal;
using System.Threading.Tasks;
using System.Web.Http;

namespace ExampleWebApi.Controllers
{
    public class ValuesController : ApiController
    {
        public async Task GetAsync()
        {
            await AuthenticateAsync(false);

            if (!(User is MyPrincipal))
            {
                throw new System.Exception("User is incorrect type.");
            }
        }

        private static async Task AuthenticateAsync(bool yield)
        {
            if (yield)
            {
                // Why is this required?
                await Task.Yield();
            }

            var principal = new MyPrincipal();
            System.Web.HttpContext.Current.User = principal;
            System.Threading.Thread.CurrentPrincipal = principal;
        }

        class MyPrincipal : GenericPrincipal
        {
            public MyPrincipal()
                : base(new GenericIdentity("<name>"), new string[] {})
            {
            }
        }
    }
}

注意事项:

  • await Task.Yield(); 可以出现在 AuthenticateAsync() 中的任何位置,也可以在 GetAsync() 之后移动到 GetAsync()调用 AuthenticateAsync() 仍然会成功.
  • ApiController.User 返回 Thread.CurrentPrincipal.
  • HttpContext.Current.User 始终正确流动,即使没有 await Task.Yield().
  • Web.config 包括 其中 暗示 UseTaskFriendlySynchronizationContext.
  • 几天前我问了一个类似的问题,但没有意识到该示例之所以成功是因为 Task.Delay(1000) 存在.
  • The await Task.Yield(); can appear anywhere in AuthenticateAsync() or it can be moved into GetAsync() after the call to AuthenticateAsync() and it will still succeed.
  • ApiController.User returns Thread.CurrentPrincipal.
  • HttpContext.Current.User always flows correctly, even without await Task.Yield().
  • Web.config includes <httpRuntime targetFramework="4.5"/> which implies UseTaskFriendlySynchronizationContext.
  • I asked a similar question a couple days ago, but did not realize that example was only succeeding because Task.Delay(1000) was present.

推荐答案

多有趣!看起来 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.

这篇关于为什么是“等待 Task.Yield()"?Thread.CurrentPrincipal 需要正确流动吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆