Подтвердить что ты не робот

Многопоточность большого количества веб-запросов в С#

У меня есть программа, в которой мне нужно создать большое количество папок на внешний сайт sharepoint (внешний смысл, который я не могу использовать объектной модели sharepoint). Веб-запросы хорошо работают для этого, но просто делать их по одному (отправить запрос, ждать ответа, повторить) довольно медленно. Я решил многопользовательский запрос, чтобы попытаться ускорить его. Программа значительно ускорилась, но через некоторое время (между 1-2 минутами или около того) забрасываются исключения concurrency.

Код ниже, это лучший способ сделать это?

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(Boolean connectFailure, Socket s4, Socket s6, Socket & socket, IPAddress & address, состояние ConnectSocketState, IAsyncResult asyncResult, тайм-аут Int32, исключение и исключение)

4b9b3361

Ответ 1

Вы можете создать слишком много соединений, таким образом, используя все локальные порты, которые вы можете использовать. Существует период времени, в течение которого порт может быть повторно использован после его закрытия. 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. Вы можете установить это значение до HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPort Ключом для использования является HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPort.

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

Ответ 2

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

Ответ 3

Для такого рода интенсивных задач ввода-вывода асинхронная модель программирования очень полезна. Тем не менее, это немного сложно использовать в С#.С# также поддерживает языковой уровень для async, вы можете попробовать 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(); 
                 });

EDIT - другой подход к веб-поиску

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