使用异步等待可以丢失http上下文吗?

时间:2015-03-17 23:52:41

标签: c# asp.net iis async-await

我们的应用程序不时因为这个错误而崩溃:https://serverfault.com/questions/675649/accessviolationexception-iis,我不想自己讨论这个错误,但对可能的原因有一些疑问:

我已经查看了带有ILSpy的system.web中的代码,我得到的结论是http运行时会丢失上下文(以及一个非托管指针),因此会崩溃。我没有关于异常的详细信息所以我检查了所有代码,除了一个api调用之外,一切都非常直接。

它或多或少看起来像这样(伪代码):

private async Task SearchAsync()
{
   Func<Stream, HttpContent, TransportContext, Task> searching = 
       async (stream, content, transportContext) =>
   {
      StreamWriter writer = new StreamWriter(stream);

      Action<Data> write = new Action<Data>()
      {   
          writer.WriteData(data);
          writer.WriteLine();
          writer.Flush();
      };

      List<Task> tasks = new List<Task>();

      foreach (IService service in services)
      {
         tasks.Add(Task.Run(() => service.CallAsync(write));
      };

      await Task.WhenAny(
         Task.Delay(10000),
         Task.WhenAll(tasks));
   };

   response.Content = new PushStreamContent(searching, "text/event-stream");
}

看起来有点奇怪但原因如下:

  • 我们需要触发一些服务,其中一半还没有任何异步客户端库。我们还在服务调用之前和之后进行一些处理,比如调用db(不是异步,感谢mongo驱动程序)和进行cpu密集型计算。
  • 每项服务可能需要很长时间(例如1分钟)才能回答。因此,我们会尽快将内容推送到响应流。
  • 我们不希望用户等待这么久,所以我们取消了对用户的请求,但继续搜索,以便下次可以使用这些数据。

所以问题是:http上下文在这段代码中是否会以某种方式丢失?丢失意味着以下内容:在链接的问题中,您会看到system.web中的本地方法失败。可能是因为传递给此方法的IntPtr(来自http上下文)不再有效

我看不到任何潜在的问题吗?如何正确处理IIS中的并行任务?


编辑:为什么service.CallAsync Async?

该代码简化了实际情况。在我的代码中,服务是真实服务的包装器。每个服务都有多个步骤,很多类和多个级别的调解器,但结构如下:

GetFromDatabase(); // Not async yet, because of mongo driver.
await CallServiceAsync(); // Sometimes async, sometimes not, depending on service.
PrepareResults(); // Not async, but cpu intensive.

因此,在我看来,将其并行化以减少响应时间是有道理的。

1 个答案:

答案 0 :(得分:5)

HttpContext存储在SynchronizationContext上。通过使用Task.Run卸载到ThreadPool线程,您没有使用SC,因此您无法访问HttpContext。

如果CallAsync是异步方法,那么您不需要在这里使用Task.Run,​​而只需存储返回的任务:

 tasks.Add(service.CallAsync(write);

如果您仍然需要Task.Run,​​您可以手动传递HttpContext:

var context = HttpContext.Current;
tasks.Add(Task.Run(() => 
{
    HttpContext.Current = context;
    service.CallAsync(write)
});