C# 为什么是“一个”呢;等待任务。屈服();Thread.CurrentPrincipal是否需要正确流动?

C# 为什么是“一个”呢;等待任务。屈服();Thread.CurrentPrincipal是否需要正确流动?,c#,asp.net-web-api,async-await,C#,Asp.net Web Api,Async Await,下面的代码已添加到新创建的Visual Studio 2012.NET 4.5 WebAPI项目中 我试图以异步方法分配HttpContext.Current.User和Thread.CurrentPrincipal。除非等待任务.Yield(),否则线程.CurrentPrincipal的赋值不正确(或任何其他异步操作)(将true传递到authenticateSync()将导致成功) 为什么呢 using System.Security.Principal; using System.Thr

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

我试图以异步方法分配
HttpContext.Current.User
Thread.CurrentPrincipal
。除非
等待任务.Yield(),否则
线程.CurrentPrincipal
的赋值不正确(或任何其他异步操作)(将
true
传递到
authenticateSync()
将导致成功)

为什么呢

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[] {})
            {
            }
        }
    }
}
使用System.Security.Principal;
使用System.Threading.Tasks;
使用System.Web.Http;
命名空间ExampleWebApi.Controllers
{
公共类值控制器:ApiController
{
公共异步任务GetAsync()
{
等待验证同步(false);
如果(!(用户是我的委托人))
{
抛出新的System.Exception(“用户类型不正确”);
}
}
私有静态异步任务同步(bool-yield)
{
如果(收益率)
{
//为什么需要这样做?
等待任务;
}
var principal=新的MyPrincipal();
System.Web.HttpContext.Current.User=主体;
System.Threading.Thread.CurrentPrincipal=主体;
}
类MyPrincipal:GenericPrincipal
{
公共机构负责人()
:base(新GenericEntity(“”),新字符串[]{})
{
}
}
}
}
注:

  • 等待任务.Yield()可以出现在
    authenticateSync()
    中的任何位置,也可以在调用
    authenticateSync()
    后移动到
    GetAsync()
    中,并且仍然会成功
  • ApiController.User
    返回
    Thread.CurrentPrincipal
  • HttpContext.Current.User
    始终正确流动,即使没有
    wait Task.Yield()
  • Web.config
    包括
    ,它
    使用TaskFriendlySynchronizationContext
  • 几天前我问过,但没有意识到这个例子之所以成功,是因为存在
    Task.Delay(1000)

多么有趣!似乎
Thread.CurrentPrincipal
基于逻辑调用上下文,而不是每线程调用上下文。在我看来,这是非常不直观的,我很想知道为什么它是以这种方式实现的


在.NET4.5.中,
async
方法与逻辑调用上下文交互,使其更适合于
async
方法。我有一个朋友;那是唯一有记录的地方。在.NET4.5中,在每个
async
方法的开头,它为其逻辑调用上下文激活“写时复制”行为。当(如果)修改逻辑调用上下文时,它将首先创建自身的本地副本

通过观察观察观察窗口中的
System.Threading.Thread.CurrentThread.ExecutionContextBelongsToCurrentScope
,可以查看逻辑调用上下文的“本地性”(即,是否已复制)

如果您没有
产生
,那么当您设置
Thread.CurrentPrincipal
时,您正在创建逻辑调用上下文的副本,该副本被视为该
异步方法的“本地”。当
async
方法返回时,本地上下文将被丢弃,原始上下文将取代它(您可以看到
ExecutionContextBelongsToCurrentScope
返回到
false

另一方面,如果您做了
屈服
,那么
同步上下文
行为将接管。实际发生的是捕获
HttpContext
,并用于恢复这两种方法。在本例中,您没有看到从
authenticateSync
GetAsync
保存的
Thread.CurrentPrincipal
;实际情况是在方法恢复之前


如果将
Yield
移动到
GetAsync
,您会看到类似的行为:
线程。CurrentPrincipal
被视为一个本地修改,作用域为
AuthenticateTasync
;当该方法返回时,它将恢复其值。但是,
HttpContext.User
仍然设置正确,并且该值将被
Yield
捕获,当方法恢复时,它将覆盖
线程。CurrentPrincipal

如果您忽略它,会发生什么?@SLaks,如果
等待任务。Yield()
被跳过,
Thread.CurrentPrincipal
恢复到调用
wait authenticateSync()之前的状态。由于
Thread.CurrentPrincipal
不再是
MyPrincipal
,因此引发了异常。在我的Owin中间件中,我必须加入最后一块中间件,它只是等待Task.Yield();这似乎会导致Thread.CurrentPrincipal在整个执行过程中与预期一致。您好!你听说过为什么要这样做吗?这篇文章我已经读了三遍了,但仍然让我神魂颠倒。@vtortola:我不确定。我假设是这样,用户权限将自动流向后台线程。这可能是十年前的事了,而更新行为的复制上下文则是最近才出现的。所以他们以这种奇怪的方式发生了冲突。