当任务继续执行但没有线程在等待时,所有活动请求都将挂起

时间:2019-03-28 16:59:00

标签: c# asp.net multithreading webforms async-await

正如标题所暗示的那样,我有这种非常不寻常的情况,我无法解释,因此作为最后的选择,我将其发布在这里。

最好立即用代码说明。

在我的IHttpHandler实现中某处存在以下情况:

var requestData = /*prepare some request data for result service...*/;

//Call the service and block while waiting for the result
MemoryStream result = libraryClient.FetchResultFromService(requestData).Result;

//write the data back in the form of the arraybuffer
HttpContext.Current.Response.BinaryWrite(CreateBinaryResponse(result));

FetchResultFromService是一种异步库方法,使用HttpClient从远程服务中检索结果。简化版本如下:

public async Task<MemoryStream> FetchResultFromService<T>(T request)
{
   ...//resolve url, formatter and media type

   //fire the request using HttpClient
   HttpResponseMessage response = await this.client.PostAsync(
                                               url,
                                               request,
                                               formatter,
                                               mediaType);
   if (!response.IsSuccessStatusCode)
   {
      throw new Exception("Exception occurred!");
   }

    //return as memory stream
    var result = new MemoryStream();
    await response.Content.CopyToAsync(result);
    result.Position = 0;
    return result;
}

FetchResultFromService响应时间超过2分钟时,就会发生此问题。如果发生这种情况,IIS将使用ThreadAbortException中止被阻止的线程(IIS超时设置为120秒)。客户端(浏览器)得到了错误响应,目前一切似乎还可以,但是不久之后,该应用的性能下降,响应时间急剧上升。

查看“库服务”的日志,很明显,服务最终完成(2分钟以上)并返回响应的那一刻是Web服务器上发生异常的那一刻。问题通常在开始后一两分钟即可解决。

希望标题现在更清晰了。 在调用者线程完成请求后,如果继续执行任务,则整个应用程序都会遭受请求。

如果我使用ConfigureAwait(false)修改库代码,则不会发生此问题。

HttpResponseMessage response = await this.client.PostAsync(
                                               url,
                                               request,
                                               formatter,
                                               mediaType).ConfigureAwait(false);//this fixes the issue

所以现在看起来它与上下文同步有关。该应用程序使用的是旧的LegacyAspNetSynchronizationContext,它确实锁定了HttpAplication对象,但我不明白这会如何影响所有线程/请求。

现在我知道上面有很多不良做法。我知道诸如Don't block on Async Code之类的文章,尽管我对收到的所有答复表示感谢,但我仍在寻找一种解释,说明在这种情况下如何使单个异步请求屈服于整个应用。

更多信息

  • 由于阻塞,我们不是在讨论死锁。如果服务设法在IIS允许的两分钟内返回结果,则上述情况将毫无问题地执行。 如果您不阻塞而只调用库方法而不调用Result getter ,甚至可以重现该问题。
  • 删除第二个等待(MemoryStream复制)会大大降低复制速度,但是仍然可以通过同时触发多个Library FetchResultFromService调用来实现。 ConfigureAwait(false)是目前唯一可靠的“解决方案”。
  • 我什至捕获了IIS,甚至显然删除了ACTIVE连接。我一直在使用JMeter对应用程序进行压力测试,并且当异常发生时,JMeter线程(虚假用户)报告该连接已重置。在使用Chrome进行手动测试时,我注意到ERR_CONNECTION_RESET响应也有所下降。这些重置通常在应用程序处于负载状态时发生,并且发生异常,但是如上所述,对于大多数活动用户而言,异常总是表现为性能下降。
  • 该应用程序是一个WebForms应用程序,该应用程序实现并使用自定义的同步IHttpHandler来处理大多数请求。 Handler实现声明为IRequiresSessionState,以促进排他会话访问。我之所以提及这一点,是因为我直觉这可能与会话状态有关,尽管我无法确认。也许它与从同步处理程序调用异步方法有关,尽管仍然想知道如何使用。
  • IIS版本为7,并且IIS日志没有显示任何有用的信息(或完全没有显示)。

我没有测试的想法,希望有人至少可以向我暗示一些方向。我真的很想了解这一点。谢谢!

2 个答案:

答案 0 :(得分:0)

不幸的是,我完全错过了一个未处理的异常,该异常导致应用程序崩溃,而我却没有意识到:

  

异常信息:System.NullReferenceException      在System.Web.ThreadContext.AssociateWithCurrentThread(Boolean)      在System.Web.HttpApplication.OnThreadEnterPrivate(Boolean)      在System.Web.LegacyAspNetSynchronizationContext.CallCallbackPossfullyUnderLock(System.Threading.SendOrPostCallback,System.Object)      在System.Web.LegacyAspNetSynchronizationContext.CallCallback(System.Threading.SendOrPostCallback,System.Object)      在System.Web.LegacyAspNetSynchronizationContext.Post(System.Threading.SendOrPostCallback,System.Object)      在System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.PostAction(System.Object)      在System.Threading.Tasks.AwaitTaskContinuation.RunCallback(System.Threading.ContextCallback,System.Object,System.Threading.Tasks.Task ByRef)      在System.Threading.Tasks.AwaitTaskContinuation + <> c.b__18_0(System.Object)      在System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(System.Object)      在System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext,System.Threading.ContextCallback,System.Object,布尔值)      在System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext,System.Threading.ContextCallback,System.Object,布尔值)      在System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()      在System.Threading.ThreadPoolWorkQueue.Dispatch()      在System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

该应用程序无法对其进行记录,并且IIS自动将其重新启动,因此我错过了它。从本质上讲,这已成为here提到的“即发即弃”问题,因为线程中止了。

希望正确使用async-await和任务友好的同步上下文不会发生这种未处理的异常。

答案 1 :(得分:0)

async / awaitLegacyAspNetSynchronizationContext一起使用的语义实际上是未定义的。换句话说,这里到处都是无数的极端情况和问题,以至于您根本无法期望任何东西能起作用。

尤其是,线程异常中止还会破坏该SynchronizationContext,然后当该await恢复时,它会在该SynchronizationContext上恢复。因此,不仅同步上下文无法与await一起正常工作,而且甚至处于部分处置和/或重用的状态。干扰其他请求上下文完全在可能性范围之内。