2009-08-08 58 views
8

我有一个ASP.NET MVC应用程序,当前使用WebClient类从控制器操作中对外部Web服务进行简单调用。异步使用ASP.NET MVC内的WebClient?

目前我正在使用同步运行的DownloadString方法。我遇到了外部Web服务无响应的问题,这导致我的整个ASP.NET应用程序处于线程饥饿和无响应状态。

解决此问题的最佳方法是什么?有一个DownloadStringAsync方法,但我不确定如何从控制器调用它。我需要使用AsyncController类吗?如果是这样,AsyncController和DownloadStringAsync方法如何交互?

感谢您的帮助。

回答

7

我认为使用AsyncControllers将帮助你在这里,因为他们卸载请求线程的处理。

我会用这样的事情(使用作为this article描述的事件模式):

public class MyAsyncController : AsyncController 
{ 
    // The async framework will call this first when it matches the route 
    public void MyAction() 
    { 
     // Set a default value for our result param 
     // (will be passed to the MyActionCompleted method below) 
     AsyncManager.Parameters["webClientResult"] = "error"; 
     // Indicate that we're performing an operation we want to offload 
     AsyncManager.OutstandingOperations.Increment(); 

     var client = new WebClient(); 
     client.DownloadStringCompleted += (s, e) => 
     { 
      if (!e.Cancelled && e.Error == null) 
      { 
       // We were successful, set the result 
       AsyncManager.Parameters["webClientResult"] = e.Result; 
      } 
      // Indicate that we've completed the offloaded operation 
      AsyncManager.OutstandingOperations.Decrement(); 
     }; 
     // Actually start the download 
     client.DownloadStringAsync(new Uri("http://www.apple.com")); 
    } 

    // This will be called when the outstanding operation(s) have completed 
    public ActionResult MyActionCompleted(string webClientResult) 
    { 
     ViewData["result"] = webClientResult; 
     return View(); 
    } 
} 

并确保你安装你需要的任何途径,例如(在Global.asax.cs中):

public class MvcApplication : System.Web.HttpApplication 
{ 
    public static void RegisterRoutes(RouteCollection routes) 
    { 
     routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 

     routes.MapAsyncRoute(
      "Default", 
      "{controller}/{action}/{id}", 
      new { controller = "Home", action = "Index", id = "" } 
     ); 
    } 
} 
+0

有没有办法给webclient异步调用添加超时? – 2011-04-19 15:55:39

+1

只是一个说明,看起来这个答案在09年发布在MVC 1.0的日子里。现在使用MVC 2/3,答案略有不同。 MapAsyncRoute方法已消失,不再需要。此外,MyAction方法现在需要重命名为MyActionAsync。否则,一切都以同样的方式工作。 – BFree 2011-06-16 21:41:57

3

的DownloadStringAsync方法使用的事件模型,提高DownloadStringCompleted当它已经完成。您也可以通过拨打WebClient.CancelAsync()来停止请求。这将使您的主要请求线程和您的WebClient线程并行运行,并允许您确定您的主线程在返回之前等待多久。

在下面的例子中,我们开始下载并设置我们要在完成时调用的事件处理程序。 DownloadStringAsync立即返回,所以我们可以继续处理剩余的请求。

为了证明在这个操作的更精细的控制,当我们达到我们的控制器动作结束后,我们可以检查,看是否下载完成尚未;如果没有,再给它3秒钟然后中止。

string downloadString = null; 

ActionResult MyAction() 
{ 
    //get the download location 
    WebClient client = StartDownload(uri); 
    //do other stuff 
    CheckAndFinalizeDownload(client); 
    client.Dispose(); 
} 

WebClient StartDownload(Uri uri) 
{ 
    WebClient client = new WebClient(); 
    client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(Download_Completed); 
    client.DownloadStringAsync(uri); 
    return client; 
} 

void CheckAndFinalizeDownload(WebClient client) 
{ 
    if(this.downloadString == null) 
    { 
     Thread.Sleep(3000); 
    } 
    if(this.downloadString == null) 
    { 
     client.CancelAsync(); 
     this.downloadString = string.Empty; 
    } 
} 

void Download_Completed(object sender, DownloadStringCompletedEventArgs e) 
{ 
    if(!e.Cancelled && e.Error == null) 
    { 
     this.downloadString = (string)e.Result; 
    } 
} 
+0

我相信这种做法仍然会阻塞请求的线程,虽然 - 你自己的睡眠通话过程中仍然是我的请求线程上。 异步控制器将在这里帮助,因为它们在等待外部资源时释放请求线程。 – 2009-08-09 04:38:54

+0

@迈克确实。这种方法将与请求线程并行运行,除非WebClient请求在控制器线程完成其他所有需要做的时间之前未完成 - 此时控制器选择是否阻止/休眠或继续。我没有得到这样的印象,即问题是如何使用AsyncController,而是如何使WebClient异步工作。这可能是对这个问题的错误解读。 – 2009-08-09 05:00:58

+0

@Rex是的,我认为从Web应用程序的描述是“线程匮乏且无响应”,可能是OP请求线程不足。将外部响应限制在3秒内会有所帮助(如果您的外部请求比以前花费的时间更长),但这仍然需要很长时间才能保持线程,因此不会扩展。 – 2009-08-09 05:13:41