Параллельно. Замерзает после 1370 итераций, не знаю, почему

Я запускаю цикл Parallel.For на чуть более 7500 объектов. Внутри этого цикла я делаю несколько вещей для каждого из этих объектов, в частности, вызывая две веб-службы и два внутренних метода. Веб-службы просто проверяют объект, обрабатывают и возвращают строку, которую затем я устанавливаю как свойство объекта. То же самое касается двух внутренних методов.

Я ничего не пишу на диск или не читаю с диска.

Я также обновляю интерфейс в приложении winforms с меткой и индикатором выполнения, чтобы пользователь мог знать, где он находится. Здесь код:

var task = Task.Factory.StartNew(() =>
{
  Parallel.For(0, upperLimit, (i, loopState) =>
  {
     if (cancellationToken.IsCancellationRequested)
        loopState.Stop();
     lblProgressBar.Invoke(
       (Action)
       (() => lblProgressBar.Text = string.Format("Processing record {0} of {1}.", (progressCounter++), upperLimit)));
     progByStep.Invoke(
       (Action)
       (() => progByStep.Value = (progressCounter - 1)));

      CallSvc1(entity[i]);
      Conversion1(entity[i]);
      CallSvc2(entity[i]);
      Conversion2(entity[i]);
  });
}, cancellationToken);

Это происходит на 32-битной машине Win7.

Любые идеи относительно того, почему это внезапно замерзает, когда инкрементер около 1370 или около того (это были 1361, 1365 и 1371)?

Любые идеи относительно того, как я могу отлаживать это и видеть, что блокировка, если что-нибудь?

EDIT:
Некоторые ответы на комментарии ниже:
@BrokenGlass - нет, нет взаимодействия. Я попробую компиляцию x86 и дам вам знать.

@chibacity - потому что это на фоновой задаче, это не замораживает пользовательский интерфейс. До тех пор, пока он не замерзнет, ​​индикатор выполнения и метка будут отмечены примерно в 2 раза в секунду. Когда он замерзает, он просто перестает двигаться. Я могу проверить, что номер, который он останавливает, обработан, но не более того. Использование ЦП на двухъядерном 2,2 ГГц минимально во время работы при 3-4% и 1-2% после замораживания.

@Henk Holterman - требуется около 10-12 минут, чтобы добраться до 1360, и да, я могу проверить, что все эти записи обработаны, но не остальные записи.

@CodeInChaos - Спасибо, я попробую! Код действительно работает, если я вынимаю параллель, это просто навсегда и на один день. Я не пробовал ограничивать количество потоков, но будет.

РЕДАКТИРОВАТЬ 2:
Некоторые сведения о том, что происходит с веб-сервисами

В основном, что происходит с веб-службами, это то, что они передают некоторые данные и получают данные (XmlNode). Этот node затем используется в процессе Conversion1, который, в свою очередь, устанавливает другое свойство в сущности, которая отправляется методу CallSvc2 и так далее. Это выглядит так:

private void CallSvc1(Entity entity)
{
    var svc = new MyWebService();
    var node = svc.CallMethod(entity.SomeProperty);
    entity.FieldToUpdate1.LoadXml(node.InnerXml);
}
private void Conversion1(Entity entity)
{
    // Do some xml inspection/conversion stuff
    if (entity.FieldToUpdate1.SelectSingleNode("SomeNode") == "something") {
        entity.FieldToUpdate2 = SomethingThatWasConverted;
    }
    else {
        // Do some more logic
    }
}
private void CallSvc2(Entity entity)
{
    var svc = new SomeOtherWebService();
    var xmlNode = svc.MethodToCall(entity.FieldToUpdate2.InnerXml);
    entity.AnotherXmlDocument.LoadXml(xmlNode.InnerXml);
}

Как вы можете видеть, это довольно простой материал. В некоторых методах преобразования много происходит, но ничто из этого не должно блокироваться. И, как отмечено ниже, в состоянии ожидания есть 1024 потока, которые все сидят на вызовах webservice. Я читал здесь http://www.albahari.com/threading/, что MaxThreads по умолчанию имеет значение 1023 для .Net 4 на 32-битной машине.

Как я могу отпустить эти ожидающие потоки, учитывая то, что у меня есть?

Ответ 1

Возможное объяснение: у вас есть процесс в состоянии, когда он не может создавать больше потоков, что мешает работе добиться прогресса, поэтому все останавливается.

Честно говоря, правильна ли эта гипотеза или нет, вы должны придерживаться совершенно другого подхода к этому. Parallel.For - неправильный способ решить эту проблему. (Parallel лучше всего подходит для работы с привязкой к ЦП. Здесь у вас есть работа с привязкой к IO.) Если вам действительно нужно иметь тысячи запросов веб-сервисов, вам нужно перейти к использованию асинхронного кода вместо многопоточный код. Если вы используете асинхронные API, вы сможете запускать тысячи запросов одновременно, используя только несколько потоков.

Возможно ли, что эти запросы будут выполняться одновременно, является другим вопросом - используете ли вы свою текущую реализацию "потокового апокалипсиса" или более эффективную асинхронную реализацию, вы вполне можете столкнуться с дросселированием. (Иногда .NET может ограничить количество запросов, которые он действительно сделает.) Таким образом, вы можете попросить сделать столько запросов, сколько захотите, но вы можете обнаружить, что почти все ваши запросы сидят в ожидании завершения предыдущих. Например. Я думаю, что WebRequest ограничивает одновременные подключения к любому отдельному домену только 2... Увольнение 1000+ потоков (или 1000 + асинхронных запросов) просто приведет к загрузке большего количества запросов, ожидающих одного из двух текущих запросов!

Вы должны делать свое собственное дросселирование. Вам нужно решить, сколько выдающихся запросов нужно одновременно, и убедитесь, что вы только запускаете сразу несколько запросов. Просто просить Parallel запускать как можно больше, насколько это возможно, все, что угодно.

Обновлено для добавления:

Быстрое исправление может заключаться в использовании перегрузки Parallel.For, которая принимает объект ParallelOptions - вы можете установить его свойство MaxDegreeOfParallelism для ограничения количества одновременных запросов. Это остановит эту поточно-тяжелую реализацию от фактического завершения потоков. Но это остается неэффективным решением проблемы. (И для всего, что я знаю, вам действительно нужно делать тысячи одновременных запросов. Если вы пишете веб-искателя, например, это действительно разумная вещь, которую вы хотите сделать. Parallel не подходит для этого класса Если вы используете поддержку APM (BeginXxx, EndXxx), вы можете обернуть это в Task объектах - Task.TaskFactory предлагает FromAsync, который предоставит задачу представляющий выполняемую асинхронную операцию.

Но если вы попытаетесь сразу запросить тысячи запросов в полете, вам нужно тщательно подумать о своей стратегии дросселирования. Просто бросать запросы туда как можно быстрее вряд ли будет оптимальной стратегией.

Ответ 2

Запустите приложение в отладчике VS. Когда он закроется, скажите VS Debug: Break All. Затем перейдите в Debug: Windows: Threads и посмотрите на потоки в вашем процессе. Некоторые из них должны показывать трассировки стека, которые находятся в вашем параллельном цикле, и это скажет вам, что они делали, когда процесс был остановлен отладчиком.