正如标题所暗示的那样,我有这种非常不寻常的情况,我无法解释,因此作为最后的选择,我将其发布在这里。
最好立即用代码说明。
在我的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之类的文章,尽管我对收到的所有答复表示感谢,但我仍在寻找一种解释,说明在这种情况下如何使单个异步请求屈服于整个应用。
Result
getter ,甚至可以重现该问题。MemoryStream
复制)会大大降低复制速度,但是仍然可以通过同时触发多个Library FetchResultFromService
调用来实现。 ConfigureAwait(false)
是目前唯一可靠的“解决方案”。ERR_CONNECTION_RESET
响应也有所下降。这些重置通常在应用程序处于负载状态时发生,并且发生异常,但是如上所述,对于大多数活动用户而言,异常总是表现为性能下降。IHttpHandler
来处理大多数请求。 Handler实现声明为IRequiresSessionState,以促进排他会话访问。我之所以提及这一点,是因为我直觉这可能与会话状态有关,尽管我无法确认。也许它与从同步处理程序调用异步方法有关,尽管仍然想知道如何使用。我没有测试的想法,希望有人至少可以向我暗示一些方向。我真的很想了解这一点。谢谢!
答案 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
/ await
与LegacyAspNetSynchronizationContext
一起使用的语义实际上是未定义的。换句话说,这里到处都是无数的极端情况和问题,以至于您根本无法期望任何东西能起作用。
尤其是,线程异常中止还会破坏该SynchronizationContext
,然后当该await
恢复时,它会在该SynchronizationContext
上恢复。因此,不仅同步上下文无法与await
一起正常工作,而且甚至处于部分处置和/或重用的状态。干扰其他请求上下文完全在可能性范围之内。