Многопоточность большого количества веб-запросов в С#
У меня есть программа, в которой мне нужно создать большое количество папок на внешний сайт 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, исключение и исключение)
Ответы
Ответ 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 вызовов веб-службы в секунду, вы исчерпаете все порты. Это является причиной этого исключения.
Хорошо, теперь как это можно исправить?
-
Одним из способов является увеличение динамического диапазона портов. Максимальное значение по умолчанию составляет 5000. Вы можете установить это значение до HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPort
Ключом для использования является HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPort
.
-
Второе, что вы можете сделать, это когда соединение действительно переходит в состояние 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", "");
}
});
});