首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >使用ASP.NET Web API时,我的ExecutionContext不会传入异步操作

使用ASP.NET Web API时,我的ExecutionContext不会传入异步操作
EN

Stack Overflow用户
提问于 2013-04-12 14:06:46
回答 1查看 6.7K关注 0票数 22

我很难理解ExecutionContext背后的机制。

根据我在网上读到的内容,上下文敏感的项目,如安全性(线程主体)、文化等,应该在执行工作单元的边界内跨异步线程流动。

然而,我遇到了非常令人困惑和潜在危险的bug。我注意到我的线程的CurrentPrincipal在异步执行时丢失了。

以下是一个示例ASP.NET Web API场景:

首先,让我们设置一个简单的Web API配置,其中包含两个用于测试的委托处理程序。

它们所做的就是写出调试信息并通过传递请求/响应,除了第一个"DummyHandler“,它设置线程的主体以及要在上下文中共享的一段数据(请求的相关ID)。

代码语言:javascript
复制
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new DummyHandler());
        config.MessageHandlers.Add(new AnotherDummyHandler());

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

public class DummyHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        CallContext.LogicalSetData("rcid", request.GetCorrelationId());
        Thread.CurrentPrincipal = new ClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new[]{ new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "dgdev") }, "myauthisthebest")));

        Debug.WriteLine("Dummy Handler Thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Debug.WriteLine("User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
        Debug.WriteLine("RCID: {0}", CallContext.LogicalGetData("rcid"));

        return base.SendAsync(request, cancellationToken)
                   .ContinueWith(task =>
                       {
                           Debug.WriteLine("Dummy Handler Thread: {0}", Thread.CurrentThread.ManagedThreadId);
                           Debug.WriteLine("User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
                           Debug.WriteLine("RCID: {0}", CallContext.LogicalGetData("rcid"));

                           return task.Result;
                       });
    }
}

public class AnotherDummyHandler : MessageProcessingHandler
{
    protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("  Another Dummy Handler Thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Debug.WriteLine("  User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
        Debug.WriteLine("  RCID: {0}", CallContext.LogicalGetData("rcid"));

        return request;
    }

    protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
    {
        Debug.WriteLine("  Another Dummy Handler Thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Debug.WriteLine("  User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
        Debug.WriteLine("  RCID: {0}", CallContext.LogicalGetData("rcid"));

        return response;
    }
}

很简单。接下来,让我们添加一个ApiController来处理HTTP POST,就像您正在上传文件一样。

代码语言:javascript
复制
public class UploadController : ApiController
{
    public async Task<HttpResponseMessage> PostFile()
    {
        Debug.WriteLine("    Thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Debug.WriteLine("    User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
        Debug.WriteLine("    RCID: {0}", CallContext.LogicalGetData("rcid"));

        if (!Request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
        }

        try
        {
            await Request.Content.ReadAsMultipartAsync(
                new MultipartFormDataStreamProvider(
                    HttpRuntime.AppDomainAppPath + @"upload\temp"));

            Debug.WriteLine("    Thread: {0}", Thread.CurrentThread.ManagedThreadId);
            Debug.WriteLine("    User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
            Debug.WriteLine("    RCID: {0}", CallContext.LogicalGetData("rcid"));

            return new HttpResponseMessage(HttpStatusCode.Created);
        }
        catch (Exception e)
        {
            return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
        }
    }
}

在使用Fiddler运行测试时,以下是我收到的输出:

代码语言:javascript
复制
Dummy Handler Thread: 63
User: dgdev
RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476

  Another Dummy Handler Thread: 63
  User: dgdev
  RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476

    Thread: 63
    User: dgdev
    RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476

    Thread: 77
    User:                                     <<<  PRINCIPAL IS LOST AFTER ASYNC
    RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476

  Another Dummy Handler Thread: 63
  User:                                       <<<  PRINCIPAL IS STILL LOST
  RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476

Dummy Handler Thread: 65
User: dgdev                                   <<<  PRINCIPAL IS BACK?!?
RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476

更令人困惑的是,当我将以下内容附加到异步行时:

代码语言:javascript
复制
await Request.Content.ReadAsMultipartAsync(
    new MultipartFormDataStreamProvider(..same as before..))
.ConfigureAwait(false); <<<<<<

我现在收到以下输出:

代码语言:javascript
复制
Dummy Handler Thread: 40
User: dgdev
RCID: 8d944500-cb52-4362-8537-dab405fa12a2

  Another Dummy Handler Thread: 40
  User: dgdev
  RCID: 8d944500-cb52-4362-8537-dab405fa12a2

    Thread: 40
    User: dgdev
    RCID: 8d944500-cb52-4362-8537-dab405fa12a2

    Thread: 65
    User: dgdev                               <<<  PRINCIPAL IS HERE!
    RCID: 8d944500-cb52-4362-8537-dab405fa12a2

  Another Dummy Handler Thread: 65
  User:                                       <<<  PRINCIPAL IS LOST
  RCID: 8d944500-cb52-4362-8537-dab405fa12a2

Dummy Handler Thread: 40
User: dgdev
RCID: 8d944500-cb52-4362-8537-dab405fa12a2

这里的重点是。async后面的代码实际上可以调用我的业务逻辑,或者只是要求正确设置安全上下文。这是一个潜在的完整性问题。

有没有人能帮我解释一下发生了什么?

提前谢谢。

EN

回答 1

Stack Overflow用户

发布于 2019-06-20 01:36:10

我知道重新进入ASP.NET同步上下文会导致Thread.CurrentPrincipal被设置为HttpContext.Current.User。但是我仍然没有看到我所期望的行为。我没想到链上的每个等待调用都会设置Thread.CurrentPrincipal = HttpContext.Current.User。我认为这甚至超出了我启动async/await链的async void事件处理程序。这是其他人看到的行为吗?我期望链上的调用使用它们捕获的上下文来继续,但它们显示出重入行为。

我没有对任何等待的呼叫使用.ContinueAwait(false)。我们在web.config中有targetFramework="4.6.1“,它在幕后设置UseTaskFriendlySynchronizationContext = true等等。第三方API客户端导致异步/等待链底部的重入行为。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15964244

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档