[英]Asynchronous web service in ASP. NET MVC
I am writing an ASP.NET MVC 5 application which among others uses web services to get/process some the data. 我正在编写一个ASP.NET MVC 5应用程序,其中包括使用Web服务来获取/处理一些数据。
The data flow of the app is following: MVC Action -> Service B -> ExtSvc which is async wrapper of a web service 该应用程序的数据流如下:MVC操作 - >服务B - > ExtSvc,它是Web服务的异步包装器
Here are some examples: 这里有些例子:
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;
}
}
Class B is the one that uses ExtSvc in a synchronous way 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
}
}
Finally the MVC action (also synchronous ) 最后是MVC动作( 同步 )
public ActionResult GetResponse(int id)
{
//some logic
B.Update(id);
//some logic
return View(...);
}
The described flow throws an error 所描述的流程会引发错误
A first chance exception of type 'System.InvalidOperationException' occurred in System.Web.dll
System.Web.dll中发生了'System.InvalidOperationException'类型的第一次机会异常
Additional information: An asynchronous operation cannot be started at this time.
附加信息:此时无法启动异步操作。 Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle.
异步操作只能在异步处理程序或模块中启动,或者在页面生命周期中的某些事件中启动。 If this exception occurred while executing a Page, ensure that the Page is marked <%@ Page Async="true" %>.
如果在执行页面时发生此异常,请确保将页面标记为<%@ Page Async =“true”%>。 This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing.
此异常还可能表示尝试调用“异步void”方法,该方法通常在ASP.NET请求处理中不受支持。 Instead, the asynchronous method should return a Task, and the caller should await it.
相反,异步方法应该返回一个Task,调用者应该等待它。
on the following line of ExtSvc : this.Service.ProcessRequestAsync(request, taskCompletionSource);
在ExtSvc的以下行:
this.Service.ProcessRequestAsync(request, taskCompletionSource);
ProcessRequestAsync
is a void method So it corresponds to: ProcessRequestAsync
是一个void方法所以它对应于:
This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing
此异常还可能表示尝试调用“异步void”方法,该方法通常在ASP.NET请求处理中不受支持
I know that converting GetResponse MVC action to asynchronous (by using async/await) and also converting the B class that actually uses ExtSvc to be asynchronous resolves the issue. 我知道将GetResponse MVC操作转换为异步(通过使用async / await)并将实际使用ExtSvc的B类转换为异步解决了这个问题。
BUT my questions is: 但我的问题是:
If I can't change the signature of B class (because of an interface it implements) to return Task<Response>
instead of Response
it basically means that I can't use async/await on it so how this issue could be resolved? 如果我不能更改B类的签名(因为它实现了一个接口)来返回
Task<Response>
而不是Response
它基本上意味着我不能使用async / await,那么如何解决这个问题呢?
ProcessRequestAsync
is void
but it's not async void
. ProcessRequestAsync
是void
但它不是async void
。 It looks like it's an EBAP API . 它看起来像是一个EBAP API 。 EBAP components generally use
AsyncOperationManager
/ AsyncOperation
, which in turn do use SynchronizationContext
to notify the underlying platform of the asynchronous operation (the last link is to my MSDN article on SynchronizationContext
). EBAP组件通常使用
AsyncOperationManager
/ AsyncOperation
,而AsyncOperationManager
/ AsyncOperation
又使用SynchronizationContext
来通知底层平台异步操作 (最后一个链接是我在SynchronizationContext
上的MSDN文章)。
The exception you're seeing is because ASP.NET sees that notification (of the asynchronous operation starting) and says "whoa, there, fella. You're a synchronous handler! No async for you!" 您看到的异常是因为ASP.NET看到该通知(异步操作开始)并说“哇,那里,家伙。你是同步处理程序!没有异步你!”
Hands-down, the best approach is to make all methods asynchronous that should be asynchronous. 动手,最好的方法是使所有方法异步,应该是异步的。 This means
B.Update
should be B.UpdateAsync
. 这意味着
B.Update
应该是B.UpdateAsync
。 OK, so there's an interface IB.Update
- just change the interface to IB.UpdateAsync
too. 好的,所以有一个接口
IB.Update
- 只需将接口更改为IB.UpdateAsync
。 Then you're async all the way, and the code is clean. 然后你一直是异步,代码很干净。
Otherwise, you'll have to consider hacks. 否则,你将不得不考虑黑客攻击。 You could use
Task.Run
as @neleus suggested - that's a way of avoiding the ASP.NET SynchronizationContext
so it doesn't "see" the asynchronous operation starting - but note that "ambient context" such as HttpContext.Current
and page culture is lost. 您可以使用
Task.Run
作为@neleus建议 - 这是避免ASP.NET SynchronizationContext
的一种方式,因此它不会“看到”异步操作开始 - 但请注意“环境上下文”,如HttpContext.Current
和页面文化是丢失。 Or, you could (temporarily) install a new SynchronizationContext()
onto the request thread - which also avoids the ASP.NET SynchronizationContext
while staying on the same thread - but some ASP.NET calls assume the presence of the ASP.NET SynchronizationContext
and will fail. 或者,您可以(暂时)将
new SynchronizationContext()
安装到请求线程上 - 这也避免了ASP.NET SynchronizationContext
同时保留在同一个线程上 - 但是一些ASP.NET调用假定存在ASP.NET SynchronizationContext
并且将失败。
There's another hack you could try; 你可以试试另一个黑客; it might work but I've never done it.
它可能会奏效,但我从来没有这样做过。 Just make your handler return a
Task<ActionResult>
and use Task.FromResult
to return the view: return Task.FromResult<ActionResult>(View(...));
只需让你的处理程序返回一个
Task<ActionResult>
并使用Task.FromResult
来返回视图: return Task.FromResult<ActionResult>(View(...));
This hack will tell ASP.NET that your handler is asynchronous (even though it's not). 这个hack会告诉ASP.NET你的处理程序是异步的(即使它不是)。
Of course, all of these hacks have the primary disadvantage that you're doing sync-over-async ( this.ExtSvc.UpdateRequest(arguments).Result
), which means you'll be using one extra unnecessary thread for the duration of each request (or two threads, if you use the Task.Run
hack). 当然,所有这些黑客都有一个主要的缺点,就是你正在进行同步异步(
this.ExtSvc.UpdateRequest(arguments).Result
),这意味着你将在每个this.ExtSvc.UpdateRequest(arguments).Result
的持续时间内使用一个额外的不必要的线程。请求(或两个线程,如果您使用Task.Run
hack)。 So you will be missing all the benefits of using asynchronous handlers in the first place - namely, scalability. 因此,您将首先忽略使用异步处理程序的所有好处 - 即可伸缩性。
I think the error occurs because your code 我认为错误发生是因为您的代码
this.Service.ProcessRequestAsync(request, taskCompletionSource);
actually calls SynchronizationContext's
OperationStarted
method that results in error as described here . 实际上调用
SynchronizationContext's
OperationStarted
方法描述导致错误这里 。
As a possible solution you can call your action on ThreadPoolSynchronizationContext
作为一种可能的解决方案,您可以在
ThreadPoolSynchronizationContext
上调用您的操作
public async Task<ActionResult> GetResponse(int id)
{
//some logic
await Task.Run(() => { B.Update(id); });
//some logic
return View(...);
}
but it adds some overhead of utilizing a thread from the pool. 但它增加了利用池中线程的一些开销。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.