我正在编写一个ASP.NET MVC 5应用程序,其中的其他应用程序使用Web服务来获取/处理一些数据。
该应用程序的数据流如下:MVC操作->服务B-> ExtSvc,它是Web服务的异步包装器
这里有些例子:
public class ExtSvc
{
//Convert Event based async pattern to task based async pattern:
private Task<Response> ProcessExtRequestAsync(Request request)
{
TaskCompletionSource<Response> taskCompletionSource =
AsyncServiceClientHelpers.CreateSource<Response>(request);
ProcessRequestCompletedEventHandler handler = null;
handler =
(sender, e) =>
AsyncServiceClientHelpers.TransferCompletion(
taskCompletionSource,
e,
() => e.Result,
() => this.Service.ProcessRequestCompleted -= handler);
this.Service.ProcessRequestCompleted += handler;
try
{
this.Service.ProcessRequestAsync(request, taskCompletionSource);
}
catch (Exception)
{
this.Service.ProcessRequestCompleted -= handler;
taskCompletionSource.TrySetCanceled();
throw;
}
return taskCompletionSource.Task;
}
//Usage:
public async Task<Response> UpdateRequest(some arguments)
{
//Validate arguments and create a Request object
var response = await this.ProcessExtRequestAsync(request)
.ConfigureAwait(false);
return response;
}
}
B类是一种以同步方式使用ExtSvc的类
public class B
{
public ExtSvc service {get; set;}
public Response Update(arguments)
{
//some logic
var result = this.ExtSvc.UpdateRequest(arguments).Result;
//some logic
return result
}
}
最后是MVC动作(也是同步的)
public ActionResult GetResponse(int id)
{
//some logic
B.Update(id);
//some logic
return View(...);
}
所描述的流程抛出错误
System.Web.dll中发生类型为'System.InvalidOperationException'的第一次机会异常
附加信息:目前无法启动异步操作。异步操作只能在异步处理程序或模块内或在页面生命周期中的某些事件期间启动。如果在执行页面时发生此异常,请确保将该页面标记为<%@ Page Async =“ true”%>。此异常也可能表示尝试调用“异步无效”方法,ASP.NET请求处理通常不支持该方法。相反,异步方法应返回一个Task,而调用者应等待它。
在ExtSvc的以下行上:this.Service.ProcessRequestAsync(request, taskCompletionSource);
ProcessRequestAsync
是一个void方法,因此它对应于:
此异常也可能表示尝试调用“异步无效”方法,ASP.NET请求处理通常不支持此方法。
我知道将GetResponse MVC操作转换为异步(通过使用async / await),并将实际使用ExtSvc的B类转换为异步也可以解决此问题。
但是我的问题是:
如果我不能更改B类的签名(由于它实现的接口)Task<Response>
而不是返回Response
它,则基本上意味着我不能对其使用async / await,那么如何解决此问题?
ProcessRequestAsync
是,void
但不是async void
。看起来它是EBAP API。EBAP组件通常使用AsyncOperationManager
/AsyncOperation
,而反过来,它们确实用于SynchronizationContext
将异步操作通知底层平台(最后一个链接是到我的MSDN文章SynchronizationContext
)。
您看到的异常是因为ASP.NET看到(异步操作开始的)通知,并说:“哇,伙计。您是一个同步处理程序!您没有异步!”
放手,最好的方法是使所有应该异步的方法异步。这意味着B.Update
应该B.UpdateAsync
。确定,所以有一个界面IB.Update
-只需将界面IB.UpdateAsync
也更改为即可。然后,您将一直处于异步状态,并且代码很干净。
否则,您将不得不考虑黑客。您可以Task.Run
按照@neleus的建议使用-这是避免ASP.NET的一种方式,SynchronizationContext
因此它不会“看到”异步操作的开始-但请注意,“环境上下文”(例如HttpContext.Current
页面文化)会丢失。或者,您可以(临时)将a安装new SynchronizationContext()
到请求线程上-这也避免了ASP.NETSynchronizationContext
停留在同一线程上-但某些ASP.NET调用假定存在ASP.NETSynchronizationContext
并将失败。
您可以尝试另一种方法。它可能有效,但我从未做到过。只需使您的处理程序返回aTask<ActionResult>
并用于Task.FromResult
返回视图:return Task.FromResult<ActionResult>(View(...));
此hack将告诉ASP.NET您的处理程序是异步的(即使不是异步的)。
当然,所有这些黑客有主要缺点是你在做同步过异步(this.ExtSvc.UpdateRequest(arguments).Result
),您将使用一个额外的不必要的线程为每个请求(或期间该装置2线,如果使用Task.Run
hack)。因此,您将首先失去使用异步处理程序的所有好处-即可伸缩性。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句