【问题标题】:Multiple web requests, start another request while waiting for response on last one. C#多个 Web 请求,在等待最后一个响应的同时启动另一个请求。 C#
【发布时间】:2019-03-18 22:29:22
【问题描述】:

我正在尝试从需要很长时间才能加载完整响应(非常长的日志文件)的网站执行多个 Web 请求。我想实现一种方法,该方法将在等待其他请求响应的同时继续创建 Web 请求并加快进程。

尝试实现一个 async-await 方法,但我认为它不会创建同时调用,而是同步运行。我对如何实现异步等待非常困惑,我查看了其他类似的问题但仍然不太清楚,我该如何修复此代码以运行异步?
这是我的代码:

        public static async Task<String> MakeRequestAsync(String url)
    {
        String responseText = null;

            try
            {
                HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
                request.Proxy = null;
                WebResponse response = request.GetResponse();
                Stream responseStream = response.GetResponseStream();
                string sr = new StreamReader(responseStream).ReadToEnd();
                response.Close();
                responseStream.Close();
                responseText = sr;
            }

            catch (Exception e)
            {
                Console.WriteLine("Error: " + e.Message);
            }
        return responseText;
    }

..

private void button_test_Click(object sender, EventArgs e)
    {
         for (int n = 0; n < 5; n++)  //multiple requests
            {
                String a = AsyncTest().Result;
            }

    }

    private async Task<String> AsyncTest()
    {
        String b = await MakeRequestAsync(url);
        return b;
    }

还有关于如何加快这个过程的任何建议都会很棒。

【问题讨论】:

    标签: c# multithreading async-await httpwebrequest


    【解决方案1】:

    您的所有 IO 调用都不是 asyncWebRequest 也没有 async/await 模式,您可以使用 BeginGetResponseAsyncCallback 获得 async 行为,但还有更好的方法。改用HttpClient,这是进行http调用的推荐方式。

    public static async Task<String> MakeRequestAsync(string url)
    {
    
        using(var client = new HttpClient()) // don't do this, inject it as a singleton
        {
           using (var response = await client.GetAsync(url)
              return await response.Content.ReadAsStringAsync();
        }  
    }
    

    然后将您的 Click 处理程序更改为 async void

    private async void button_test_Click(object sender, EventArgs e)
    {
         var tasks = new List<Task<string>>();
         for (int n = 0; n < 5; n++)  //multiple requests
         {
            tasks.Add(MakeRequestAsync(url));
         }
         await Task.WhenAll(tasks); // wait for all of them to complete;
         foreach(var task in tasks)
         {
             var str = await task; // or task.Result, won't block, already completed;
         }
    }
    

    【讨论】:

    • " WebRequest 也没有异步/等待模式" - 真的吗? +1
    • 这是有效的,它等待第一次调用的响应,同时发出其他请求,但过了一段时间,第一次调用永远不会完成(慢请求)并抛出 System.Threading。 Tasks.TaskCanceledException: '任务被取消。'异常,这是请求中的某种超时吗?
    • @BLM 听起来像是一个新问题?
    • @BLM 是的,这是一个超时问题。你需要在http客户端设置Timeout属性
    【解决方案2】:

    您应该使用 Microsoft 的响应式框架(又名 Rx)- NuGet System.Reactive 并添加 using System.Reactive.Linq; - 然后您可以这样做:

    var query =
        from n in Observable.Range(0, 5)
        from r in Observable.Using(
            () => new WebClient(),
            wc => Observable.Start(() => wc.DownloadString($"{url}{n}")))
        select new { n, r };
    
    IDisposable subscription =
        query
            .ToArray()
            .Select(xs => xs.OrderBy(x => x.n).Select(x => x.r).ToArray())
            .Subscribe(rs =>
            {
                /* Do something with the results */
            });
    

    这都是多线程和非阻塞的。

    如果您不关心返回结果的顺序,那么只需执行以下操作:

    var query =
        from n in Observable.Range(0, 5)
        from r in Observable.Using(
            () => new WebClient(),
            wc => Observable.Start(() => wc.DownloadString($"{url}{n}")))
        select r;
    
    IDisposable subscription =
        query
            .Subscribe(r =>
            {
                /* Do something with each result */
            });
    

    如果您需要取消查询,调用 subscription.Dispose() 将停止查询。


    你可以这样写你的MakeRequestAsync方法:

    public static async Task<String> MakeRequestAsync(String url)
    {
        return await
            Observable.Using(
                () => new WebClient(),
                wc => Observable.Start(() => wc.DownloadString(url)));
    }
    

    【讨论】:

    • 此解决方案是否会使用 5 个线程在下载字符串时阻塞?
    • 它就像一个魅力,虽然如果我尝试从这个方法返回字符串 r 我得到一个错误:“匿名函数转换为 void 返回委托不能返回值”。为了访问字符串,我可以从该方法执行什么类型的返回?
    • @BLM WebClient.DownloadString 是一个阻塞操作。这意味着您需要启动后台线程,这些线程只是闲逛并等待结果的到来。这意味着您正在无缘无故地抢夺工作线程的线程池。
    • @JohanP - WebClient.DownloadString 如果在当前线程上调用是阻塞操作,但 Rx 会推送到后台线程。您可以完全控制所使用的日程安排。
    • @BLM - 我已经为您的 MakeRequestAsync 方法添加了代码。
    猜你喜欢
    • 1970-01-01
    • 2017-12-13
    • 2022-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-14
    相关资源
    最近更新 更多