【问题标题】: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.UserThread.CurrentPrincipalThread.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 包括&lt;httpRuntime targetFramework="4.5"/&gt; 其中implies UseTaskFriendlySynchronizationContext
  • 几天前我问过a similar question,但没有意识到这个例子之所以成功,是因为Task.Delay(1000) 存在。

【问题讨论】:

  • 如果你忽略它会发生什么?
  • @SLaks,如果 await Task.Yield() 被跳过,Thread.CurrentPrincipal 将恢复到调用 await AuthenticateAsync() 之前的状态。由于Thread.CurrentPrincipal 不再是MyPrincipal,因此抛出异常。
  • 在我的 Owin 中间件中,我必须链接最后一个中间件,它只是等待 Task.Yield();这似乎会导致 Thread.CurrentPrincipal 在整个执行过程中按预期运行。

标签: c# asp.net-web-api async-await


【解决方案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.CurrentPrincipalAuthenticateAsync 保留到 GetAsync;在方法恢复之前实际发生的是HttpContext is preserved, and then HttpContext.User is overwriting Thread.CurrentPrincipal

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

【讨论】:

  • 嗨!您是否听说过为什么要以这种方式实施?这篇文章我已经读了 3 遍了,仍然让我大吃一惊。
  • @vtortola:我不确定。我认为这是为了让用户权限自动流向后台线程。这可能在十年前就已经完成了,并且更新时复制上下文的行为要更新得多。所以他们以这种奇怪的方式发生冲突。
猜你喜欢
  • 2016-02-04
  • 1970-01-01
  • 2011-01-14
  • 1970-01-01
  • 2018-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-04
相关资源
最近更新 更多