mvc异步怎么把操作放到后台线程

2025-05-18 12:46:01
推荐回答(1个)
回答1:

  MVC支持异步地处理请求。可以通过下面的三种方式:
  实现一个自定义的RouteHandler,并为GetHttpHandler方法返回一个实现IHttpAsynHandler的对象。
  创建一个自定义的基类Controller,并实现IAsyncController,IAsyncController是IController的异步版本。
  MVC内置了一个AsyncController,它实现了上述的IAsyncController,通过简单的继承AsyncController,即可实现异步。
  下面仅对第三种方法作简单介绍。假设某个Action需要调用一个web service,并处理结果后返回:
  01 public ContentResult GetPhotoByTag(string tag)
  02 {
  03 ...
  04 using (var response = WebRequest.Create(url).GetResponse())
  05 {
  06 // Parse the response as XML
  07 var xmlDoc = XDocument.Load(XmlReader.Create(response.GetResponseStream()));
  08 ...
  09 }
  10 ...
  11 }
  显然,如果这个web request消耗2s,那么这个请求将hold这个线程至少2s。这种同步的处理方式显然不合理,想要异步处理,只需按如下步骤进行:
  1、替换基类Controller为AsyncController
  2、创建两个配对的Action:ActionNameAsync和ActionNameCompleted。ActionNameAsync方法必须返回void,在内部启动一个耗时的IO操作前,需要使用AsyncManager.OutstandingOperations.Increment()向MVC框架“注册启动”,在IO方法返回后,可以在AsyncManager.Parameters字典中保存希望传给ActionNameCompleted方法的参数。最后调用AsyncManager.OutstandingOperations.Decrement()通知MVC框架操作完成,此时,MVC框架会自动调用ActionNameCompleted。ActionNameCompleted需要向通常的Action一样,返回一个ActionResult。因此上面的代码需要改写成如下这样:
  01 public void GetPhotoByTagAsync(string tag)
  02 {
  03 //向MVC中注册启动
  04 AsyncManager.OutstandingOperations.Increment();
  05 ...
  06 WebRequest request = WebRequest.Create(url);
  07 //启动一个异步的web request
  08 request.BeginGetResponse(asyncResult =>
  09 {
  10 using (WebResponse response = request.EndGetResponse(asyncResult))
  11 {
  12 var xml = XDocument.Load(XmlReader.Create(response.GetResponseStream()));
  13 ...
  14 //将结果photoUrls,保存在AsyncManager.Parameters中
  15 AsyncManager.Parameters["photoUrls"] = photoUrls;
  16 //通知MVC框架操作完成 ,准备调用Completed
  17 AsyncManager.OutstandingOperations.Decrement();
  18 }
  19 }, null);
  20 }
  21 //像通常的Action一样,这里的参数photoUrls将在AsyncManager.Parameters中匹配
  22 public ContentResult GetPhotoByTagCompleted(IEnumerable photoUrls)
  23 {
  24 return Content(string.Format("", photoUrls.First()));
  25 }
  当然,可以设置异步操作的超时时间:
  1 [AsyncTimeout(10000)] // 10000 milliseconds equals 10 seconds
  2 public void GetPhotoByTagAsync(string tag) { ... }
  上面的代码如果超时了,将抛出TimeoutException异常,我们可以用希望的方式处理它。
  当使用类似BeginGetResponse这类的异步方法,并提供回调函数参数时,你无法控制回调函数调用在哪个线程上。大多数情况下,甚至不在ASP.NET的工作线程上。所以回调函数无法关联原始的HttpContext对象。
  幸好,AsyncManager提供了一个Sync()方法,它会将一个委托在ASP.NET的工作线程上启动,并关联原始的HttpContext对象。而且它保证线程安全:
  01 BeginAsyncOperation(asyncResult => {
  02 var result = EndAsyncOperation(asyncResult);
  03 // Can't always access System.Web.HttpContext.Current from here...
  04 Action doSomethingWithHttpContext = () => {
  05 // ... but can always access it from this delegate
  06 };
  07 if (asyncResult.CompletedSynchronously) // Already on an ASP.NET thread
  08 doSomethingWithHttpContext();
  09 else // Must switch to an ASP.NET thread
  10 AsyncManager.Sync(doSomethingWithHttpContext);
  11 AsyncManager.OutstandingOperations.Decrement();
  12 }, null);