C# 多次等待后,控制器中的HTTPContext.Current为null

C# 多次等待后,控制器中的HTTPContext.Current为null,c#,asp.net-mvc,async-await,C#,Asp.net Mvc,Async Await,这是我在一些遗留代码中遇到的一个奇怪的问题,我已经将其标记为re-factor。我已经检查过了,代码没有使用。ConfigureAwait(false) 所讨论的代码看起来很像:(testx=..”行是调试问题以公开行为的一部分。) 返回True,True,True,但随后将其更改为: var eeoGroups = _lookupService.GetEEOGroups(); results.Add(System.Web.HttpContext.Current != null); eeoGro

这是我在一些遗留代码中遇到的一个奇怪的问题,我已经将其标记为re-factor。我已经检查过了,代码没有使用
。ConfigureAwait(false)

所讨论的代码看起来很像:(testx=..”行是调试问题以公开行为的一部分。)

返回True,True,True,但随后将其更改为:

var eeoGroups = _lookupService.GetEEOGroups();
results.Add(System.Web.HttpContext.Current != null);
eeoGroups = _lookupService.GetEEOGroups();
results.Add(System.Web.HttpContext.Current != null);
eeoGroups = _lookupService.GetEEOGroups();
results.Add(System.Web.HttpContext.Current != null);
返回False,False,False

再深入一点,我注意到这些方法混合了EntityFramework和旧的基于NHibernate的存储库代码。是EntityFramework异步方法在等待时绊倒了上下文

在等待后触发上下文的方法之一:

public async Task<List<string>> GetEEOGroups()
{
    return await _dbContext.EmployeeEEOGroup.GroupBy(e => e.EEOGroup).Select(g => g.FirstOrDefault().EEOGroup).ToListAsync();
}
public异步任务GetEEOGroups()
{
返回wait _dbContext.employeeogroup.GroupBy(e=>e.EEOGroup)。选择(g=>g.FirstOrDefault().EEOGroup)。tolistSync();
}
同样:*编辑-复制/粘贴的whups:)

公共异步任务GetAllHHS() { return await_dbContext.HHS.Where(x=>x.IsActive.ToListAsync(); } 但这很好:

public async Task<IEnumerable<Decision>> GetAllDecisions()
{
    return await Task.FromResult(_repository.Session.QueryOver<Lookup>().Where(l => l.Type == "Decision" && l.IsActive).List().Select(l => new Decision { DecisionId = l.Id, Description = l.Name }).ToList());
}
公共异步任务GetAllDecisions() { 返回wait Task.FromResult(_repository.Session.QueryOver().Where(l=>l.Type==“Decision”&&l.IsActive).List().Select(l=>newdecision{DecisionId=l.Id,Description=l.Name}).ToList(); } 通过查看“有效”的代码,可以很清楚地看出,在给定Task.FromResult的情况下,它实际上并没有针对同步方法执行任何异步操作。我认为最初的作者被async silver bullet的诱惑所吸引,只是为了保持一致性而包装了旧代码。EF的异步方法与await一起工作,但HttpContext似乎支持async/await

年,有一项关于这个问题的全面研究和问题的映射,即问题的根源:

HttpContext对象存储所有与请求相关的数据,包括 指向本机IIS请求对象ASP.NET管道的指针 实例、请求和响应属性、会话(以及许多 其他)。如果没有所有这些信息,我们可以安全地说出我们的代码 不知道正在执行的请求上下文。设计 完全无状态的web应用程序并不容易,实现它们并不容易 这的确是一项具有挑战性的任务。此外,web应用程序具有丰富的 第三方库,通常是黑匣子 未指定的代码运行

思考一件非常有趣的事情,程序中如此关键和基本的对象,
HttpContext
是如何在请求执行过程中丢失的


提供的解决方案:

它包括
TaskScheduler
ExecutionContext
SynchronizationContext

  • TaskScheduler
    正是您所期望的。任务调度程序!(我会成为一名优秀的教师!)这取决于.NET的类型
    使用应用程序时,特定的任务调度程序可能比
    其他的。默认情况下,ASP.NET使用ThreadPoolTaskScheduler,即 针对吞吐量和并行后台处理进行了优化
  • ExecutionContext
    (EC)在某种程度上与该名称的含义相似。您可以将其视为TLS(线程本地)的替代品 存储)用于多线程并行执行。在极端合成中,
    它是用于持久化所需的所有环境上下文的对象 使代码运行,并保证方法可以 在不同的线程上中断并恢复,不会造成损害(均来自
    逻辑和安全视角)。需要理解的关键方面是
    EC需要“流动”(本质上是从线程复制到
    另一种)每当发生代码中断/恢复时
  • 相反,
    SynchronizationContext
    (SC)更难掌握。它与欧共体有关,在某些方面与欧共体相似,尽管 实施更高层次的抽象。事实上,它可以持续
    环境状态,但它有专用于
    在特定环境中排队/出列工作项。多亏了 SC,开发人员可以编写代码,而不必担心 运行时处理异步/等待模式

如果你从博客的例子中考虑代码:

   await DoStuff(doSleep, configAwait)
        .ConfigureAwait(configAwait);

    await Task.Factory.StartNew(
        async () => await DoStuff(doSleep, configAwait)
            .ConfigureAwait(configAwait),
        System.Threading.CancellationToken.None,
        asyncContinue ? TaskCreationOptions.RunContinuationsAsynchronously : TaskCreationOptions.None,
        tsFromSyncContext ? TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Current)
        .Unwrap().ConfigureAwait(configAwait);  
解释:

  • configAwait
    :控制等待任务时的ConfigureAwait行为(有关其他注意事项,请继续阅读)
  • tsFromSyncContext
    :控制传递给StartNew方法的TaskScheduler选项。如果为true,则TaskScheduler是根据当前 SynchronizationContext,否则将使用当前TaskScheduler
  • doSleep
    :如果为True,DoStuff将在线程上等待。Sleep。如果为False,则等待HttpClient.GetAsync操作。如果要测试,此操作非常有用
    它没有互联网连接
  • asyncContinue
    :控制传递给StartNew方法的TaskCreationOptions。如果为true,则继续异步运行。
    如果您还计划测试后续任务并评估 嵌套等待操作情况下的任务内联行为
    (不影响LegacyASPNETSynchronizationContext)
深入阅读这篇文章,看看它是否符合你的问题,我相信你会在里面找到有用的信息



还有另一个解决方案,使用嵌套容器,您也可以检查它。

如果您注释掉test1->test3并从test4开始,会发生什么情况?我们更新了问题的更多细节。罪魁祸首似乎是实体框架的异步方法,因为我没有发现EF与旧包装的Task.FromResult在同步调用方面的混合。据我所知,async/await应该与HttpCon一起使用
public async Task<IEnumerable<SapHHS>> GetAllHHS()
{
    return await _dbContext.HHS.Where(x => x.IsActive).ToListAsync();
}
public async Task<IEnumerable<Decision>> GetAllDecisions()
{
    return await Task.FromResult(_repository.Session.QueryOver<Lookup>().Where(l => l.Type == "Decision" && l.IsActive).List().Select(l => new Decision { DecisionId = l.Id, Description = l.Name }).ToList());
}
   await DoStuff(doSleep, configAwait)
        .ConfigureAwait(configAwait);

    await Task.Factory.StartNew(
        async () => await DoStuff(doSleep, configAwait)
            .ConfigureAwait(configAwait),
        System.Threading.CancellationToken.None,
        asyncContinue ? TaskCreationOptions.RunContinuationsAsynchronously : TaskCreationOptions.None,
        tsFromSyncContext ? TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Current)
        .Unwrap().ConfigureAwait(configAwait);