为什么Thread.CurrentPrincipal需要“等待Task.Yield()”?

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

我试图在asynchronous方法中分配HttpContext.Current.UserThread.CurrentPrincipalThread.CurrentPrincipal的分配不正确,除非await Task.Yield(); (或其他任何asynchronous)被执行(传递trueAuthenticateAsync()将导致成功)。

这是为什么?

 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()任何地方,也可以在调用AuthenticateAsync()之后移入GetAsync() AuthenticateAsync() ,它仍然会成功。
  • ApiController.User返回Thread.CurrentPrincipal
  • 即使不await Task.Yield()HttpContext.Current.User也会始终正确地stream动。
  • Web.config包含<httpRuntime targetFramework="4.5"/>这意味着 UseTaskFriendlySynchronizationContext
  • 几天前我问了一个类似的问题 ,但是没有意识到这个例子只是因为Task.Delay(1000)出现而成功了。

多么有趣! 看来Thread.CurrentPrincipal是基于逻辑调用上下文的,而不是每个线程的调用上下文。 海事组织这是非常不直观的,我很想知道为什么这样实施。


在.NET 4.5中, async方法与逻辑调用上下文进行交互,以便更好地与async方法一起stream动。 我有一个关于这个话题的博客文章 ; AFAIK是唯一的logging。 在.NET 4.5中,在每个async方法的开始处,它会为其逻辑调用上下文激活“写入时复制”行为。 当(如果)逻辑调用上下文被修改时,它将首先创build自己的本地副本。

您可以通过在监视窗口中观察System.Threading.Thread.CurrentThread.ExecutionContextBelongsToCurrentScope来看到逻辑调用上下文的“本地性”(即是否已被复制)。

如果你没有Yield ,那么当你设置Thread.CurrentPrincipal ,你正在创build一个逻辑调用上下文的副本,这个副本被视为“ async方法的“本地”。 当async方法返回时,该本地上下文被丢弃,原始上下文取代它(可以看到ExecutionContextBelongsToCurrentScope返回false )。

另一方面,如果你做了Yield ,那么SynchronizationContext行为就会接pipe。 实际发生的是HttpContext被捕获并用于恢复两个方法。 在这种情况下,您看不到从AuthenticateAsync保存到GetAsync Thread.CurrentPrincipal ; 实际发生的事情是保留HttpContext ,然后HttpContext.User覆盖Thread.CurrentPrincipal方法恢复。

如果将Yield转换为GetAsync ,则会看到类似的行为:将Thread.CurrentPrincipal作为范围为AuthenticateAsync的本地修改处理; 当该方法返回时,它将恢复其值。 但是, HttpContext.User仍然是正确设置的,并且该值将被Yield捕获,当方法恢复时,它将覆盖Thread.CurrentPrincipal