Многопоточность большого количества веб-запросов в c # - PullRequest
23 голосов
/ 25 ноября 2010

У меня есть программа, в которой мне нужно создать большое количество папок для внешнего сайта sharepoint (внешний смысл - я не могу использовать объектную модель sharepoint). Веб-запросы хорошо работают для этого, но просто выполнять их по одному (отправить запрос, ждать ответа, повторить) довольно медленно. Я решил многопоточность запросов, чтобы попытаться ускорить его. Программа значительно ускорилась, но через некоторое время (между 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 (EndPoint endPointSnapshot, SocketAddre ss socketAddress)
в System.Net.Sockets.Socket.InternalConnect (EndPoint remoteEP)
в System.Net.ServicePoint.ConnectSocketInternal (логическое connectFailure, сокет s4, сокет s6, сокет и сокет, IP-адрес и адрес, состояние ConnectSocketState, IAsyncResult asyncResult, Тайм-аут Int32, Исключение и исключение)

Ответы [ 4 ]

21 голосов
/ 25 ноября 2010

Вы можете создать слишком много соединений, таким образом, используя все локальные порты, которые вы можете использовать.Существует период ожидания, когда порт может быть повторно использован после его закрытия.WebRequest скрывает всю низкоуровневую обработку сокетов для вас, но я предполагаю, что в конце концов она исчерпает порты или пытается (пере) связать сокет уже в состоянии TIME_WAIT.

ВыУбедитесь, что вы прочитали поток ответов , даже если вас не волнует ответ.Это должно помочь не создавать слишком много длительных соединений.

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

Я вставлю соответствующую информацию из здесь :

Когда соединение закрыто,на стороне, которая закрывает соединение, 5 кортежей {Протокол, Локальный IP, Локальный порт, Удаленный IP, Удаленный порт} переходит в состояние TIME_WAIT на 240 секунд по умолчанию.В этом случае протокол является фиксированным - TCP локальный IP, удаленный IP и удаленный PORT также обычно являются фиксированными.Таким образом, переменная является локальным портом.Что происходит, когда вы не привязываете, используется порт в диапазоне 1024-5000.Итак, примерно у вас есть 4000 портов.Если вы используете их все в течение 4 минут, то есть примерно в течение 4 минут вы совершаете 16 вызовов веб-службы в секунду, вы исчерпаете все порты.Это является причиной этого исключения.

Хорошо, как это можно исправить?

  1. Одним из способов является увеличение диапазона динамического порта.Максимальное значение по умолчанию - 5000. Вы можете установить это значение до 65534. HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPort - это ключ для использования.

  2. Второе, что вы можете сделать, это как только соединение установится вВ состоянии TIME_WAIT вы можете сократить время, в течение которого оно находится в этом состоянии. По умолчанию это 4 минуты, но вы можете установить его на 30 секунд HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\TCPTimedWaitDelay - ключ для использования.Установите 30 секунд

10 голосов
/ 25 ноября 2010

Вы не закрываете веб-запрос, который может привести к чрезмерно долгому открытию соединения.Это похоже на идеальную работу для 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 и повторные попытки подключения, которые могут привести к превышению времени ожидания, что приведет к более сложному коду

2 голосов
/ 25 ноября 2010

Для такого рода интенсивных задач ввода-вывода модель асинхронного программирования очень полезна. Тем не менее, это немного сложно использовать в C #. C # также имеет поддержку на уровне языка для асинхронного теперь, вы можете попробовать CTP release

1 голос
/ 25 ноября 2010

попробуйте

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

РЕДАКТИРОВАТЬ - другой подход веб-запроса

folderPathList.ToList().ForEach(p =>
        {
            ThreadPool.QueueUserWorkItem((o) =>
                 {
                     using (WebClient client = new WebClient())
                     {
                         client.Credentials = DefaultCredentials;
                         client.UploadString(p, "MKCOL", "");
                     }
                 });
        });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...