【问题标题】:Multithreading a large number of web requests in c#在c#中多线程处理大量Web请求
【发布时间】:2010-11-25 13:59:15
【问题描述】:

我有一个程序,我需要在其中创建大量文件夹到外部共享点站点(外部意味着我不能使用共享点对象模型)。 Web 请求在这方面工作得很好,但一次只做一个(发送请求、等待响应、重复)相当慢。我决定对请求进行多线程处理,以尝试加快速度。程序已经大大加快了速度,但经过一段时间(1-2 分钟左右)后,开始抛出并发异常。

代码如下,这是最好的方法吗?

Semaphore Lock = new Semaphore(10, 10);
List<string> folderPathList = new List<string>();
//folderPathList populated

foreach (string folderPath in folderPathList)
{
    Lock.WaitOne();
    new Thread(delegate()
    {
        WebRequest request = WebRequest.Create(folderPath);
        request.Credentials = DefaultCredentials;
        request.Method = "MKCOL";

        WebResponse response = request.GetResponse();
        response.Close();
        Lock.Release();
    }).Start();
}
for(int i = 1;i <= 10;i++)
{
    Lock.WaitOne();
}

例外情况类似于

未处理的异常:System.Net.WebException:无法连接到远程服务器 ---> System.Net.Sockets.SocketException:每个套接字地址通常只允许使用一次 192.0.0.1:81
在 System.Net.Sockets.Socket.DoConnect(端点 endPointSnapshot,SocketAddre ss socketAddress)
在 System.Net.Sockets.Socket.InternalConnect(EndPoint remoteEP)
在 System.Net.ServicePoint.ConnectSocketInternal(布尔连接失败,Socket s4,Socket s6,Socket& 套接字,IPAddress& 地址,ConnectSocketState 状态, IAsyncResult asyncResult, Int32 timeout, Exception&异常)

【问题讨论】:

  • 您应该发布异常消息,以便这里的人可以帮助您

标签: c# multithreading webrequest


【解决方案1】:

您可能会创建过多的连接,从而耗尽所有可以使用的本地端口。 关闭端口后可以重用端口有一个超时期限。 WebRequest 为您隐藏了所有低级套接字处理,但我猜它最终会耗尽端口,或者尝试(重新)绑定到已经处于 TIME_WAIT 状态的套接字。

您应该确保阅读响应流,即使您不关心响应。这应该有助于不产生过多的延迟连接。

WebResponse response = request.GetResponse();
new StreamReader(response.GetResponseStream()).ReadToEnd(); 

我会从here粘贴一些相关信息:

当连接关闭时,在关闭连接的一侧,5 元组 { Protocol, Local IP, Local Port, Remote IP, Remote Port} 默认进入 TIME_WAIT 状态 240 秒。 在这种情况下,协议是固定的——TCP 本地 IP、远程 IP 和远程端口通常也是固定的。所以变量是本地端口。 发生的情况是,当您不绑定时,将使用 1024-5000 范围内的端口。 所以大致你有4000个端口。如果您在 4 分钟内使用所有这些 - 大致意思是您 每秒进行 16 次 Web 服务调用,持续 4 分钟,您将耗尽所有端口。这就是这个异常的原因。

好的,现在如何解决这个问题?

  1. 其中一种方法是增加动态端口范围。默认最大值为 5000。您可以将其设置为 65534。 HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPort是使用的关键。

  2. 您可以做的第二件事是,一旦连接确实进入 TIME_WAIT 状态,您可以减少它的时间 在该状态下,默认为 4 分钟,但您可以将其设置为 30 秒 HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\TCPTimedWaitDelay 是使用的关键。 将此设置为 30 秒

【讨论】:

    【解决方案2】:

    您没有关闭 Web 请求,这可能会导致连接打开的时间过长。这听起来像是 Parallel.Net 的 Parallel.Foreach 的完美工作,只需确保指明您希望它在多少个线程上运行

      ParallelOptions parallelOptions = new ParallelOptions();
    
            parallelOptions.MaxDegreeOfParallelism = 10;
            Parallel.ForEach(folderPathList, parallelOptions, folderPathList =>
            {
                using(WebRequest request = WebRequest.Create(folderPath))
                {
                   request.Credentials = DefaultCredentials;
                   request.Method = "MKCOL";
    
                   GetResponse request = WebRequest.Create(folderPath);
                   request.Credentials = DefaultCredentials;
                   request.Method = "MKCOL";
                   using (WebResponse response = request.GetResponse());
                }
            });
    

    另外要记住的是 maxConnections,一定要在你的 app.config 中设置它:

    <configuration>
      <system.net>
        <connectionManagement>
          <add address = "*" maxconnection = "100" />
        </connectionManagement>
      </system.net>
    </configuration>
    

    当然,在现实世界的场景中,您必须添加 try-catch 并重试连接,这可能会超时导致更复杂的代码

    【讨论】:

    • 啊,这确实是一个很好的解决方案,但不幸的是我使用的是 .NET 3.5 框架。
    • 太糟糕了,WebRequest 不是一次性的
    • 我很困惑,因为我正在关闭请求,但是在 App.config 中设置 maxconnection 为我做到了。谢谢!
    【解决方案3】:

    对于这种 IO 密集型任务,asynchronous programming model 非常有用。但是,在 C# 中使用有点困难。C# 现在也有语言级别的异步支持,你可以试试CTP release

    【讨论】:

      【解决方案4】:

      试试这个

      folderPathList.ToList().ForEach(p =>
              {
                  ThreadPool.QueueUserWorkItem((o) =>
                       {
                           WebRequest request = WebRequest.Create(p);
                           request.Credentials = DefaultCredentials;
                           request.Method = "MKCOL";
                           WebResponse response = request.GetResponse();
                           response.Close(); 
                       });
      

      编辑 - 不同的 webrequest 方法

      folderPathList.ToList().ForEach(p =>
              {
                  ThreadPool.QueueUserWorkItem((o) =>
                       {
                           using (WebClient client = new WebClient())
                           {
                               client.Credentials = DefaultCredentials;
                               client.UploadString(p, "MKCOL", "");
                           }
                       });
              });
      

      【讨论】:

      • 比我当前的解决方案更简洁的代码方式,但这确实会引发相同的错误。
      • 不应该有并发问题,所以我修改了网络请求方法,看看它是否更适合你
      猜你喜欢
      • 2021-04-09
      • 2019-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多