【问题标题】:Using WebClient within ASP.NET MVC asynchronously?在 ASP.NET MVC 中异步使用 WebClient?
【发布时间】:2009-08-08 15:43:29
【问题描述】:

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

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

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

感谢您的帮助。

【问题讨论】:

    标签: asp.net-mvc asynchronous multithreading webclient


    【解决方案1】:

    我认为使用 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 = "" }
            );
        }
    }
    

    【讨论】:

    • 有没有办法为 webclient 异步调用添加超时?
    • 请注意,这个答案似乎是在 MVC 1.0 天的 09 年发布的。现在有了 MVC 2/3,答案略有不同。 MapAsyncRoute 方法已消失,不再需要。此外,MyAction 方法现在需要重命名为 MyActionAsync。否则,一切都以同样的方式工作。
    【解决方案2】:

    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;
        }
    }
    

    【讨论】:

    • 我相信这种方法仍然会阻塞请求线程——在你的睡眠调用期间你仍然在请求线程上。异步控制器在这里会有所帮助,因为它们会在您等待外部资源时释放请求线程。
    • @Michael 确实如此。这种方法将与请求线程并行运行,除非 WebClient 请求在控制器线程完成它需要做的所有其他事情时尚未完成——此时控制器选择是阻塞/睡眠还是继续。我没有得到这样的印象,即这个问题专门针对如何使用 AsyncController,而是如何使 WebClient 异步工作。这可能是对问题的错误解读。
    • @Rex 是的,我认为从网络应用程序“线程匮乏且无响应”的描述来看,很可能 OP 的请求线程已用完。将外部响应限制在 3 秒内会有所帮助(如果您的外部请求之前花费的时间比这更长),但是保持线程仍然需要很长时间,因此它不会扩展。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-29
    • 1970-01-01
    相关资源
    最近更新 更多