为啥 Thread.CurrentPrincipal 需要“等待 Task.Yield()”才能正确流动?
Posted
技术标签:
【中文标题】为啥 Thread.CurrentPrincipal 需要“等待 Task.Yield()”才能正确流动?【英文标题】:Why is an "await Task.Yield()" required for Thread.CurrentPrincipal to flow correctly?为什么 Thread.CurrentPrincipal 需要“等待 Task.Yield()”才能正确流动? 【发布时间】:2013-05-15 05:37:59 【问题描述】:以下代码已添加到新创建的 Visual Studio 2012 .NET 4.5 WebAPI 项目中。
我正在尝试以异步方法同时分配 HttpContext.Current.User
和 Thread.CurrentPrincipal
。 Thread.CurrentPrincipal
的分配不正确,除非执行 await Task.Yield();
(或其他任何异步操作)(将 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()
中,它仍然会成功。
ApiController.User
返回Thread.CurrentPrincipal
。
HttpContext.Current.User
始终正确流动,即使没有 await Task.Yield()
。
Web.config
包括<httpRuntime targetFramework="4.5"/>
其中implies UseTaskFriendlySynchronizationContext
。
几天前我问过a similar question,但没有意识到这个例子之所以成功,是因为Task.Delay(1000)
存在。
【问题讨论】:
如果你忽略它会发生什么? @SLaks,如果await Task.Yield()
被跳过,Thread.CurrentPrincipal
将恢复到调用 await AuthenticateAsync()
之前的状态。由于Thread.CurrentPrincipal
不再是MyPrincipal
,因此抛出异常。
在我的 Owin 中间件中,我必须链接最后一个中间件,它只是等待 Task.Yield();这似乎会导致 Thread.CurrentPrincipal 在整个执行过程中按预期运行。
【参考方案1】:
多么有趣! Thread.CurrentPrincipal
似乎是基于 logical 调用上下文,而不是每个线程调用上下文。 IMO 这很不直观,我很想知道为什么要以这种方式实施。
在 .NET 4.5. 中,async
方法与逻辑调用上下文交互,因此它可以更正确地与async
方法一起使用。我有一个blog post on the topic; AFAIK 这是唯一记录它的地方。在 .NET 4.5 中,在每个 async
方法的开头,它会为其逻辑调用上下文激活“写时复制”行为。当(如果)逻辑调用上下文被修改时,它将首先创建自己的本地副本。
您可以通过在监视窗口中观察System.Threading.Thread.CurrentThread.ExecutionContextBelongsToCurrentScope
来查看逻辑调用上下文的“本地性”(即是否已被复制)。
如果您没有Yield
,那么当您设置Thread.CurrentPrincipal
时,您将创建逻辑调用上下文的副本,该副本被视为该async
方法的“本地”。当async
方法返回时,该本地上下文将被丢弃并取而代之的是原始上下文(您可以看到ExecutionContextBelongsToCurrentScope
返回到false
)。
另一方面,如果您执行Yield
,则SynchronizationContext
行为会接管。实际发生的是HttpContext
被捕获并用于恢复这两种方法。在这种情况下,您没有看到 Thread.CurrentPrincipal
从 AuthenticateAsync
保留到 GetAsync
;在方法恢复之前实际发生的是HttpContext
is preserved, and then HttpContext.User
is overwriting Thread.CurrentPrincipal
。
如果将Yield
移动到GetAsync
,您会看到类似的行为:Thread.CurrentPrincipal
被视为范围为AuthenticateAsync
的本地修改;当该方法返回时,它会恢复其值。但是,HttpContext.User
仍然设置正确,并且该值将被Yield
捕获,当方法恢复时,它将覆盖Thread.CurrentPrincipal
。
【讨论】:
嗨!您是否听说过为什么要以这种方式实施?这篇文章我已经读了 3 遍了,仍然让我大吃一惊。 @vtortola:我不确定。我认为这是为了让用户权限自动流向后台线程。这可能在十年前就已经完成了,并且更新时复制上下文的行为要更新得多。所以他们以这种奇怪的方式发生冲突。以上是关于为啥 Thread.CurrentPrincipal 需要“等待 Task.Yield()”才能正确流动?的主要内容,如果未能解决你的问题,请参考以下文章
为啥使用 glTranslatef?为啥不直接更改渲染坐标?
为啥 DataGridView 上的 DoubleBuffered 属性默认为 false,为啥它受到保护?