【问题标题】:multiple webrequest through a string array通过字符串数组进行多个 webrequest
【发布时间】:2015-06-26 16:37:49
【问题描述】:

我有超过 2000 个 url 调用要进行,并且使用下面的代码需要将近 2 分钟才能完成。有人可以帮我加快这个过程吗?

private void button4_Click(object sender, EventArgs e)
    {
        WebRequest req;
        WebResponse res;
        string[] lines = File.ReadAllLines(@"c:\data\temp.txt");
        for (int i = 0; i < lines.Count(); i++)
        {
            req = WebRequest.Create(lines[i]); 
            res = req.GetResponse();
            StreamReader rd = new StreamReader(res.GetResponseStream(), Encoding.ASCII);
            rd.Close();
            res.Close();
            textBox1.Text += ".";
        }
    } 

非常感谢

【问题讨论】:

  • 购买更快的互联网。
  • 穿线它。您可以同时拨打更多电话。

标签: c# arrays webrequest webresponse


【解决方案1】:

我将建议您为此使用 Microsoft 的响应式框架。 NuGet“Rx-Main”、“Rx-WinForms”/“Rx-WPF”。

代码如下所示:

private void button4_Click(object sender, EventArgs e)
{
    var query =
        from line in File.ReadAllLines(@"c:\data\temp.txt").ToObservable()
        from result in Observable.Defer(() =>
        {
            var req = WebRequest.Create(line);
            return
                Observable.Using(
                    () => req.GetResponse(),
                    res => Observable.Using(
                        () => new StreamReader(res.GetResponseStream(), Encoding.ASCII),
                        st => Observable.Start(() => st.ReadToEnd())));
        })
        select new { line, result };

    query
        .ObserveOn(textBox1)
        .Subscribe(x => textBox1.Text += ".");
}

我假设您正在尝试从流中读取字符串。

这段代码很好地处理了所有中间对象。它还正确地对请求进行多线程处理,并将结果编组到 UI 线程并更新文本框文本。

这段代码稍微干净一点的版本是这样的:

private void button4_Click(object sender, EventArgs e)
{
    var query =
        from line in File.ReadAllLines(@"c:\data\temp.txt").ToObservable()
        from result in Observable.Using(
            () => new WebClient(),
            wc => Observable.Start(() => wc.DownloadString(new Uri(line))))
        select new { line, result };

    query
        .ObserveOn(textBox1)
        .Subscribe(x => textBox1.Text += ".");
}

它使用WebClient 进行下载。它仍然可以根据需要进行多线程处理。

【讨论】:

    【解决方案2】:

    您无法加快速度,因为瓶颈是您的互联网连接。但是,您可以做一些事情:

    1) 不要 LINQ 计算行数,它是一个数组并且它的大小是已知的(微优化,您永远不会注意到这种变化)。

    2) 使用using 释放一次性对象(与速度无关,更好的错误处理:如果您的代码出现问题,您将使用 GC 释放资源)。

    3) 使它们平行。这会加快速度一点点

    private void button4_Click(object sender, EventArgs e)
    {
        var lines = File.ReadAllLines(@"c:\data\temp.txt");
    
        var options = new ParallelOptions { MaxDegreeOfParallelism = 4 };
        Parallel.ForEach(lines, options, line => 
        {
            var request = WebRequest.Create(line);
    
            using (var response = request.GetResponse()) 
            {
                var reader = new StreamReader(response.GetResponseStream(), Encoding.ASCII);
    
                // Do your stuff
    
                BeginInvoke(new MethodInvoker(delegate 
                {
                    textBox1.Text += ".";
                }));
            }
        });
    } 
    

    还有一些注意事项:

    • MaxDegreeOfParallelism 设置最大并发请求数。多个活动的并发连接不会无限期地加快速度,甚至可能减慢速度。一些试验会帮助您将此值设置为一个合理的值。

    • 没有任何错误检查,但网络问题可能会暂时出错,但经过​​短暂的延迟后,它们可能会按预期工作。我建议还阅读 System.Net.WebException: The remote name could not be resolvedthis 以了解 I/O 操作。

    为了使其成为一个更完整的示例,您的点击事件处理程序将是:

    private void button4_Click(object sender, EventArgs e)
    {
        var options = new ParallelOptions { MaxDegreeOfParallelism = 4 };
        Parallel.ForEach(ReadUrlList(@"c:\data\temp.txt"), options, ProcessUrl);
    }
    

    处理每个 URL 和读取 URL 列表的实际代码:

    private static string[] ReadUrlList(string path)
    {
        return File.ReadAllLines(@"c:\data\temp.txt");
    }
    
    private void ProcessUrl(string url)
    {
        ProcessResponse(response =>
        {
            using (var reader = new StreamReader(response.GetResponseStream(), Encoding.ASCII))
           {
                // Do your stuff
    
                // We're working on separate threads, to access UI we
                // have to dispatch the call to UI thread. Note that
                // code will be executed asynchronously then local
                // objects may have been disposed!
                BeginInvoke(new MethodInvoker(delegate 
                {
                    textBox1.Text += ".";
                }));
            }
        });
    } 
    

    使用此辅助方法隐藏网络操作的尝试/等待模式:

    private static void ProcessResponse(string url, Action<WebResponse> action) 
    {
        for (int i=1; i <= NumberOfRetries; ++i) 
        {
            try 
            {
                var request = WebRequest.Create(line);
    
                using (var response = request.GetResponse()) 
                {
                    action(response);
                }
    
                break;
            }
            catch (Exception e) 
            {
                if (i == NumberOfRetries)
                    throw;
    
                Thread.Sleep(DelayOnRetry);
            }
        }
    }
    
    private const int NumberOfRetries = 3;
    private const int DelayOnRetry = 1000;
    

    【讨论】:

    • 我还会考虑禁用代理查找(除非需要)和关闭 NAGLE。
    【解决方案3】:

    由于您没有指定框架版本,我假设您至少使用 4.5。

    您可以使用ActionBlock 轻松地同时执行多个调用。 ActionBlock 在单个线程中执行其动作方法,并且可以同时执行多个执行。

    你可以这样使用:

    var options=new ExecutionDataflowBlockOptions
    {
        MaxDegreeOfParallelism = 10
    }
    
    var block=new ActionBlock<string>(url=>
    {
        using(var req = WebRequest.Create(url))
        using(var res = req.GetResponse())
        {
           //Process the response here   
        }
    });
    
    string[] lines = File.ReadAllLines(@"c:\data\temp.txt");
    foreach(var line in lines)
    {
        block.Post(line);
    }
    
    block.Complete();
    
    await block.Completion;
    

    您可以通过更改 MaxDegreeOfParallelism 方法来控制并发请求的数量。

    您也可以调用GetResponseAsync异步执行请求。这不会使它们运行得更快,但会减少用于服务相同数量请求的 ThreadPool 线程的数量。这意味着在阻塞和上下文切换时浪费的 CPU 更少。

    var block=new ActionBlock<string>(url=>async 
    {
        using(var req = WebRequest.Create(url))
        using(var res = await req.GetResponseAsync())
        {
           //Process the response here   
        }
    });
    

    处理请求和响应是重要的。除非您处理响应,否则与服务器的连接将保持活动状态。 .NET 强制每个域(即 URL)限制 2 个并发请求,因此孤立响应可能会导致延迟,直到垃圾收集器运行并收集它们。虽然您可以覆盖限制,但最好始终处理响应。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-11-20
      • 1970-01-01
      • 2018-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-03
      • 1970-01-01
      相关资源
      最近更新 更多