【问题标题】:Using HttpContext in Async Task在异步任务中使用 HttpContext
【发布时间】:2012-12-06 16:38:51
【问题描述】:

我有以下 mvc 动作。

public async Task<JsonResult> DoSomeLongRunningOperation()
{
    return await Task.Run(() =>
    {
        //Do a lot of long running stuff
        //The underlying framework uses the HttpContext.Current.User.Identity.Name so the user is passed on the messagebus.
    }
}

在任务中,HttpContext 为空。我们做了很多欺骗,但没有任何东西可以保证 HttpContext 在我们的新线程中始终可用。

有没有在异步任务中使用 HttpContext 的解决方案?

在我们的 IocContainer 中,我们注册了以下将用户名传递给框架的对象。

public class HttpContextUserIdentityName : ICredentials
{
    public string Name
    {
        get { return HttpContext.Current.User.Identity.Name; }
    }
}

在持久化到数据库之前,很多地方都会调用此代码。

我们需要另一种方法来获取发起 web 请求的用户的用户名,或者解决 HttpContext 为空的问题。

因为持久化到数据库发生在任务中,所以在进入任务之前我无法访问 HttpContext。

我也想不出一种安全的方法来临时保留用户名,以便实现另一个 ICredentials 服务对象。

【问题讨论】:

标签: c# asp.net-mvc asp.net-mvc-3 asynchronous async-await


【解决方案1】:

您几乎不想在 ASP.NET 方法中使用 Task.Run

我认为最简洁的解决方案(但工作最多)是在您的其他层实现async-兼容接口:

public async Task<JsonResult> DoSomeLongRunningOperation()
{
  //Do a lot of long running stuff
  var intermediateResult = await DoLongRunningStuff();
  return await DetermineFinalResult(intermediateResult);
}

【讨论】:

  • 当然,代码在架构的不同层,但这不是这里的问题。如果我在不同的类中定义它,我仍然会遇到同样的问题。
  • 真的很糟糕的答案。有很多 API 需要 Task。
  • @TheSharpNinja:我会坚持我的回答。如果您需要Task,请使用async/await,而不是Task.Run
  • 如果被调用的函数必须返回 void,这是不可能的。使用 async void 会导致异常处理问题。在这种情况下,使用 Task.Run 是正确的技术。现在,我对此进行了很多思考,并使用将线程关联到上下文对象的自定义调度程序来解决问题。
  • @TheSharpNinja:也许您可以详细说明您的场景并发布您自己的问题?对于这种情况,Task.Run 听起来不是一个好的解决方案。
【解决方案2】:

您应该在开始新线程之前从当前上下文中获取所需的任何信息。在这种情况下,添加如下内容:

string username = HttpContext.Current.User.Username;

Task.Run 之前,然后在另一个线程中使用它。

顺便说一句,就目前而言,没有理由await 任务。您可以直接返回任务而不将方法标记为Async

如果您需要访问Response 对象,这可能会利用长时间运行的操作的结果,因此不能在Task.Run 之前,您应该在@ 之后 987654327@(但确保任务是awaited)。如果您最终这样做,那么您将无法执行我在上一段中建议的操作。

【讨论】:

  • 我更新了我的问题...需要访问任务中的上下文或需要其他安全的解决方案来获取我的用户名,然后再将内容保存到数据库。
  • @MarcoFranssen 您将无法从后台线程访问当前上下文,这不会发生。正如我已经说过的,您需要在启动后台线程之前从当前上下文中获取信息。另一个问题可能是你在后台任务中做的比你应该做的更多。也许该方法的某些部分应该完全重构以在其他地方运行,或者只有实际与数据库交互或进行某些处理的较小部分应该设置为在后台任务中运行。这里的问题是混合业务和 UI 代码。
  • @Servy 您实际上可以关闭 HttpContext 对象作为新任务的状态对象。这应该在堆栈上为新线程创建该对象的新副本。请参阅下面的答案。
  • @hotSauce.Open 我在对你的帖子的评论中列出了几个问题;除此之外,您实际上并没有在代码中关闭它,您只是将它作为形式参数传入。那是……不同。
【解决方案3】:

我会尝试将 HttpContext 的引用作为状态对象传递,因为这应该在堆栈上为执行工作的线程创建该对象的新实例。不要使用 Task.Run,​​而是使用

return await Task.Factory.StartNew((ctx) => 
{
    var context = (HttpContext)ctx;
   //Do stuff
}, httpContextObject);

Task.Run 和 Task.Factory.StartNew 立即返回,因此当您的线程对已释放的对象进行操作时,asp.net 在处理请求的工作线程中的事件生命周期中继续运行。

【讨论】:

  • 首先,您不是在创建对象的副本,您只是在复制对对象的引用HttpContext 不是 struct。其次,它不能从后台线程访问是有原因的,原因是它不是为从其他上下文访问而设计的。它不是设计上的线程安全对象,当您处于适当的同步上下文等时,您只能依赖对象的当前状态。您根本不 supposed 访问@987654325 @ 来自另一个线程,就像在 winform 应用程序中您不能从非 UI 线程访问控件一样。
  • @Servy 根据this similar question and answer,可以关闭HttpContext。如果每个线程都有自己的堆栈并且 HttpContext 作为参数传递给该线程,那么应该在堆上为新线程创建该对象的副本。
  • 确定可以关闭它。我从来没有说过你不能我说你不应该。是的,每个线程都有自己的堆栈,我对此没有异议。我要争辩的是,您所做的是以任何方式复制上下文;它不是。虽然堆栈不同(根据需要),但 heap 是共享的。由于HttpContext 是一个引用类型,实际对象位于堆上。您只是在闭包中复制 对该对象的引用
  • 好的,这可能不是上述问题的答案,但对我很有用,谢谢! :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-24
  • 2017-10-30
  • 1970-01-01
相关资源
最近更新 更多