Я работаю с .NET с момента его создания и занимаюсь параллельным программированием задолго до этого ... тем не менее, я затрудняюсь объяснить этот феномен.Этот код работает в производственной системе и выполняет свою работу по большей части, просто ищет лучшего понимания.
Я передаю 10 URL для одновременной обработки в следующее:
public static void ProcessInParellel(IEnumerable<ArchivedStatus> statuses,
StatusRepository statusRepository,
WaitCallback callback,
TimeSpan timeout)
{
List<ManualResetEventSlim> manualEvents = new List<ManualResetEventSlim>(statuses.Count());
try
{
foreach (ArchivedStatus status in statuses)
{
manualEvents.Add(new ManualResetEventSlim(false));
ThreadPool.QueueUserWorkItem(callback,
new State(status, manualEvents[manualEvents.Count - 1], statusRepository));
}
if (!(WaitHandle.WaitAll((from m in manualEvents select m.WaitHandle).ToArray(), timeout, false)))
throw ThreadPoolTimeoutException(timeout);
}
finally
{
Dispose(manualEvents);
}
}
Обратный вызов выглядит примерно так:
public static void ProcessEntry(object state)
{
State stateInfo = state as State;
try
{
using (new LogTimer(new TimeSpan(0, 0, 6)))
{
GetFinalDestinationForUrl(<someUrl>);
}
}
catch (System.IO.IOException) { }
catch (Exception ex)
{
}
finally
{
if (stateInfo.ManualEvent != null)
stateInfo.ManualEvent.Set();
}
}
Каждый из обратных вызовов смотрит наURL-адрес и следует за серией перенаправлений (для обработки файлов cookie AllowAutoRedirect намеренно установлено значение false):
public static string GetFinalDestinationForUrl(string url, string cookie)
{
if (!urlsToIgnore.IsMatch(url))
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
request.AllowAutoRedirect = false;
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
request.Method = "GET";
request.KeepAlive = false;
request.Pipelined = false;
request.Timeout = 5000;
if (!string.IsNullOrEmpty(cookie))
request.Headers.Add("cookie", cookie);
try
{
string html = null, location = null, setCookie = null;
using (WebResponse response = request.GetResponse())
using (Stream stream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(stream))
{
html = reader.ReadToEnd();
location = response.Headers["Location"];
setCookie = response.Headers[System.Net.HttpResponseHeader.SetCookie];
}
if (null != location)
return GetFinalDestinationForUrl(GetAbsoluteUrlFromLocationHeader(url, location),
(!string.IsNullOrEmpty(cookie) ? cookie + ";" : string.Empty) + setCookie);
return CleanUrl(url);
}
catch (Exception ex)
{
if (AttemptRetry(ex, url))
throw;
}
}
return ProcessedEntryFlag;
}
У меня есть высокоточный StopWatch вокруг рекурсивного вызова GetFinalDestinationForUrl с порогом 6 секунд, и обычнозавершенные обратные вызовы делают это в течение этого времени.
Однако WaitAll со значительным таймаутом (0,0,60) для 10 потоков по-прежнему регулярно истекает.
Исключение выводит что-то вроде:
System.Exception: Не все потоки возвращаются за 60 секунд: Макс. Рабочий: 32767, Макс. Ввод / вывод: 1000, Доступный рабочий: 32764, ДоступенI / O: 1000 при Work.Threading.ProcessInParellel (состояния IEnumerable`1, StatusRepository statusRepository, обратный вызов WaitCallback, время ожидания TimeSpan) в Work.UrlExpanderWorker.SyncAllUsers ()
Это работает в .NET 4 с maxConnections , установленным на 100 для всех URL.
Моя единственная теория заключается в том, что это возможно для синхронногоВызов HttpWebRequest для блокировки дольше указанного времени ожидания?Это единственное разумное объяснение.Вопрос в том, почему и как лучше принудительно установить реальный тайм-аут для этой операции?
Да, я знаю, что рекурсивный вызов определяет время ожидания 5 с для каждого вызова, но для обработки может потребоваться несколько вызовов.данный URL.Но я почти не вижу предупреждений StopWatch.На каждые 20-30 ошибок тайм-аута WaitAll, которые я вижу, может появиться одно сообщение, указывающее, что данный поток занимал более 6 секунд.Если проблема действительно в том, что 10 потокам в совокупности требуется более 60 секунд, то я должен увидеть как минимум 1: 1 корреляцию (если не выше) между сообщениями.
ОБНОВЛЕНИЕ (30 марта,2012):
Я могу подтвердить, что одни сетевые вызовы не учитывают таймауты при определенных обстоятельствах:
Uri uri = new Uri(url);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.AllowAutoRedirect = false;
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
request.Method = "GET";
request.KeepAlive = false;
request.Pipelined = false;
request.Timeout = 7000;
request.CookieContainer = cookies;
try
{
string html = null, location = null;
using (new LogTimer("GetFinalDestinationForUrl", url, new TimeSpan(0, 0, 10)))
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (Stream stream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(stream))
{
html = reader.ReadToEnd();
location = response.Headers["Location"];
cookies = Combine(cookies, response.Cookies);
if (response.ContentLength > 150000 && !response.ContentType.ContainsIgnoreCase("text/html"))
log.Warn(string.Format("Large request ({0} bytes, {1}) detected at {2} on level {3}.", response.ContentLength, response.ContentType, url, level));
}
Этот код регулярно регистрирует записи, которые заняли 5-6 минут до завершения И были не больше 150000. И я не говорю об изолированном сервере здесь или там, это случайные (громкие) медиа-сайты.
Что именно здесь происходит иКак мы можем гарантировать, что код завершится в разумные сроки?