为什么Thread.CurrentPrincipal需要“等待Task.Yield()”?
下面的代码被添加到新创build的Visual Studio 2012 .NET 4.5 WebAPI项目中。
我试图在asynchronous方法中分配HttpContext.Current.User
和Thread.CurrentPrincipal
。 Thread.CurrentPrincipal
的分配不正确,除非await Task.Yield();
(或其他任何asynchronous)被执行(传递true
到AuthenticateAsync()
将导致成功)。
这是为什么?
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
。