【问题标题】:is HttpContext async safe in asp.net core?HttpContext 异步在 asp.net 核心中是安全的吗?
【发布时间】:2019-09-09 21:58:44
【问题描述】:

根据我阅读的内容,asp.net core 已删除同步上下文。这意味着在await调用之后执行代码的线程可能与在await之前执行代码的线程不同

那么在async 方法中使用HttpContext 是否仍然安全?还是可以在await 调用后获得不同的上下文?

例如在控制器动作中

public async Task<IActionResult> Index()
{
    var context1 = HttpContext;
    await Task.Delay(1000);
    var context2 = HttpContext;
    ....
}

context1 可能与 context2 不同吗?

在无控制器方法中获取上下文的推荐方法是依赖注入IHttpContextAccessor

IHttpContextAccessor.HttpContextasync await 模式安全吗?

I.E. context1 可以与 context2 不同吗?

public async void Foo(IHttpContextAccessor accessor)
{
    var context1 = accessor.HttpContext;
    await Task.Delay(1000);
    var context2 = accessor.HttpContext;
}

【问题讨论】:

  • 如果您在可以直接访问HttpContext 的控制器中,请使用此选项。如果您在服务中或其他没有直接 HttpContext 的地方使用 IHttpContextAccessor.HttpContext ,它被设计为异步安全的。
  • @ckuri 有没有这方面的文档?似乎无法理解 asp.net 核心如何在没有 SynchronizationContext 的情况下恢复对 HttpContext 的引用的逻辑
  • Internally 访问器使用AsyncLocal,它沿异步调用图保留信息。那里的示例显示了一个没有 SynchronisationContext 的控制台应用程序。 ASP .NET Core documentation 只有在他们说当你不等待异步调用时访问器不起作用时才隐含地提到这一点。

标签: c# asynchronous asp.net-core


【解决方案1】:

那么在异步方法中使用 HttpContext 是否仍然安全?还是可以在 await 调用之后获得不同的上下文?

asyncHttpContext 以及 ASP.NET pre-Core 的全部问题是由于代码通常从 HttpContext.Current 获得 HttpContext。 ASP.NET 是一个多线程服务器,每个await 可以在不同的线程上恢复。因此,在异步代码恢复之前,ASP.NET pre-Core 必须有一个 AspNetSynchronizationContext 托管设置 HttpContext.Current

现代 ASP.NET Core 没有同步上下文。但这很好,因为它没有HttpContext.Current。获取HttpContext 实例的唯一方法是通过本地属性(例如,控制器类上的HttpContext)或依赖注入(IHttpContextAccessor)。

(学究式注释:上面的解释有点简化了——ASP.NET 预核心同步上下文确实处理了除HttpContext.Current 之外的其他事情——但同样的总体解释适用于它的所有其他职责——即,它们是在核心世界中不需要)

因此,上下文不可能不同。它们是相同的属性 - 相同的对象实例。 ASP.NET pre-Core 的问题是 static 属性值 HttpContext.Current,已在 ASP.NET Core 中删除。

【讨论】:

  • 你是异步相关问题的英雄
  • @Steve 出于好奇,我想知道您是否可以在 AsyncLocal 上添加一些评论,以及为什么这不适合在 ASP.NET Core 中实现静态 HttpContext.Current(假设 HttpContext.Current)。当前是只读的) - 因为乍一看它似乎在技术上仍然可行,即使使用多线程;也许还有“自以为是的设计”因素需要考虑?
  • @nicholas: AsyncLocal&lt;T&gt; 可用于实现静态HttpContext.Current。我相信它没有完成的主要原因是因为自以为是的设计。准全局和隐式状态通常不被认为是一个好的设计,尽管该一般规则有例外。通过注入IHttpContextAccessor,组件显式声明了它对该隐式状态的依赖。
【解决方案2】:

根据文档:https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-5.0#do-not-access-httpcontext-from-multiple-threads

HttpContext 不是线程安全的。从多个访问 HttpContext 并行线程可能导致未定义的行为,例如挂起, 崩溃和数据损坏。

不要这样做:以下示例发出三个并行请求 并在传出 HTTP 之前和之后记录传入请求路径 要求。从多个线程访问请求路径, 可能并行。

public class AsyncBadSearchController : Controller
{       
    [HttpGet("/search")]
    public async Task<SearchResults> Get(string query)
    {
       var query1 = SearchAsync(SearchEngine.Google, query);
       var query2 = SearchAsync(SearchEngine.Bing, query);
       var query3 = SearchAsync(SearchEngine.DuckDuckGo, query);

       await Task.WhenAll(query1, query2, query3);

       var results1 = await query1;
       var results2 = await query2;
       var results3 = await query3;

       return SearchResults.Combine(results1, results2, results3);
    }       

    private async Task<SearchResults> SearchAsync(SearchEngine engine, string query)
    {
        var searchResults = _searchService.Empty();
        try
        {
            _logger.LogInformation("Starting search query from {path}.", 
                                    HttpContext.Request.Path);
            searchResults = _searchService.Search(engine, query);
            _logger.LogInformation("Finishing search query from {path}.", 
                                    HttpContext.Request.Path);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed query from {path}", 
                             HttpContext.Request.Path);
        }

        return await searchResults;
    }
}

执行此操作:以下示例复制传入的所有数据 在发出三个并行请求之前请求。

public class AsyncGoodSearchController : Controller
{       
    [HttpGet("/search")]
    public async Task<SearchResults> Get(string query)
    {
        string path = HttpContext.Request.Path;
        var query1 = SearchAsync(SearchEngine.Google, query,
                             path);
        var query2 = SearchAsync(SearchEngine.Bing, query, path);
        var query3 = SearchAsync(SearchEngine.DuckDuckGo, query, path);

        await Task.WhenAll(query1, query2, query3);

        var results1 = await query1;
        var results2 = await query2;
        var results3 = await query3;

        return SearchResults.Combine(results1, results2, results3);
    }

       private async Task<SearchResults> SearchAsync(SearchEngine engine, string query,
                                                  string path)
       {
        var searchResults = _searchService.Empty();
        try
        {
            _logger.LogInformation("Starting search query from {path}.",
                                   path);
            searchResults = await _searchService.SearchAsync(engine, query);
            _logger.LogInformation("Finishing search query from {path}.", path);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed query from {path}", path);
        }

        return await searchResults;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-25
    • 1970-01-01
    • 1970-01-01
    • 2020-10-12
    • 2017-07-28
    • 2012-07-22
    • 2015-07-21
    相关资源
    最近更新 更多