Parallel.ForEach - принудительное прерывание журнала, выполняющего блокирующие вызовы - PullRequest
0 голосов
/ 14 января 2011

Не рад этой невозможности прерывания потока вообще.Пример;

У вас есть простое приложение Windows Form, которое подключается к блокирующему синхронному веб-сервису.В рамках которого он выполняет функцию веб-службы в параллельном цикле.

Parallel.ForEach(iListOfItems, po, (item, loopState) =>
    {

        ParallelProcessIntervalRequest(wcfProxyClient, item, loopState);

    });

Вызов веб-службы занимает 2 минуты для завершения в avg (в действительности это может быть любой блокирующий вызов, например Thread.Sleep).вместо этого, не просто веб-сервис) Теперь я установил свой MaxDegreeOfParallelism на прагматические 20 потоков.В iListOfItems есть 1000 элементов для обработки.

Пользователь нажимает кнопку процесса, и цикл начинается, очень приятно, что у нас есть 20 потоков, работающих на 1000 элементов в коллекции iListOfItems.Отлично.

Однако пользователю по какой-то причине необходимо закрыть приложение, а затем закрыть форму.Эти 20 потоков будут продолжать превышать все 1000 элементов, что было бы не хорошо, если бы до сих пор обрабатывалось, может быть, только 40, а теперь это было бы довольно плохо, поскольку приложение не будет закрываться, как того ожидает пользователь, но будет продолжать работать засцены, как видно в диспетчере задач.

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

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

Поэтому я решил не вызывать функцию Cancel объекта CancellationTokenSource, поскольку это вызывает исключение, которое является дорогостоящим, а также, возможно, нарушает антипаттен управленияпоток кода по исключениям.Поэтому я реализую простое свойство, безопасное для потоков - StopExecuting.Внутри цикла я проверяю значение StopExecuting и, если установлено значение true внешним воздействием, я делаю следующий вызов из цикла;

if (loopState.ShouldExitCurrentIteration || loopState.IsExceptional || StopExecuting) {loopState.Stop(); return;}

Таким образом, итерация может завершиться «контролируемым» способом вместе состановка цикла, обрабатывающего дальнейшие итерации, но, как я уже сказал, это мало что дает для нашей дилеммы.

Длинный блокирующий вызов, выполняемый в итерации, должен завершиться, прежде чем мы сможем проверить, должны ли мы остановиться.Таким образом, когда пользователь закрывает форму, возможно, 20 потоков попросили остановить, но они остановятся только после того, как завершат выполнение там длительного вызова функции - что может составить 2 минуты в среднем.

То же самоеверно для вызова Отмена на CancellationTokenSource.Только после того, как итерация закончена (не прервана, как прерывание потока), исключение кэшируется, готово к тому времени, когда все другие потоки в конечном итоге завершаются и возвращаются.Вызов Cancel при CancellationTokenSource, по-видимому, не создает исключение в потоке обработки, которое прервало бы блокирующий вызов, как прерывание потока.

Если это произойдет, это в любом случае не будет отличаться от вызова прерывания потока в потоке, поскольку оба способа приводят к исключению, которое может быть перехвачено в потоке, чтобы иметь дело с закрытием и освобождением ресурсов и т. д. до выхода из потока.

Исключение прерывания потока - это то место, где вы можете смягчить требование "оставляя систему в нестабильном / неопределенном состоянии ", если произойдут непредвиденные обстоятельства, такие как закрытие формы, чтобы впоследствии сказать, что иногда это может быть невозможным, и программисту действительно нужно убедиться в том, что они кодируют код.цикл и, действительно, какие ресурсы они выбрали для поддержки.Принимая во внимание все обстоятельства, невозможность прерывать блокирующие вызовы с помощью прерывания потока, подобного поведению, кажется, что инструмент был потерян из-за нашей хитрости.Мне пришлось бы вернуться к нормальным конструкциям потоков в таких случаях, чтобы получить эту единственную, но важную способность.Позор.

Так это проблема, короткое замыкание в новой параллельной библиотеке? Если нет, то как библиотека позволяет нам убивать эти потоки, когда форма закрыта, не дожидаясь блокировки вызовов или не оставляя процесс запущенным за кулисами. Конечно, такой контроль был бы относительно простым, если бы мы использовали «старые» потоковые примитивы и реальные потоки.

Ответы [ 3 ]

0 голосов
/ 14 января 2011

IMO, это не проблема параллельной библиотеки.С каждой поточной конструкцией у вас в основном одни и те же принципиальные проблемы: если вы хотите аккуратный способ прерывания, вам придется реализовать его самостоятельно.Если бы вы использовали «обычный» поток и использовали сигнальную систему для спасения, у нее возникла бы та же проблема, что и для параллельной библиотеки: если бы она только что ввела блокирующий вызов продолжительностью две минуты, потребовалось бы две минуты, прежде чем произойдет спасение.

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

В качестве отступления: я ни в коем случае не эксперт по параллельной библиотеке, но разве потоки, порожденные этой библиотекой, не реализованы как фоновые потоки, то есть: не должны ли они автоматически срываться при завершении работы приложения?

0 голосов
/ 14 января 2011

Одна из лучших вещей в TPL - это его расширяемость. Если вам что-то не нравится, скорее всего, есть способ заменить часть, которая вам не нравится. Если вы хотите использовать другую семантику организации очередей, используйте собственную фабрику Task; если вы хотите более жесткий контроль над реальными потоками, их приоритетами, состоянием квартиры, ... реализуйте пользовательский TaskScheduler.

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

Пример в моем блоге или в MSDN.

0 голосов
/ 14 января 2011

Нет, это не недостаток PTL.

Слон в комнате - это те двухминутные звонки веб-службы; они не должны так долго ждать.

Все, что может сделать PTL - это код вызова. Если этот код блокируется, он не может сигнализировать о его остановке, поэтому должен ждать или прерывать поток. Я полагаю, что может убивать потоки и тому подобное, но это опасно и вызовет гораздо больше возмущения, чем невозможности, поскольку люди будут использовать его неправильно (помните Thread.IsBackground?).

Идея 1

Личным выбором было бы сделать вызов веб-службы, который просто помещает сообщение в очередь (или использует ESB, такой как NServiceBus) и немедленно возвращает его. Это сообщение будет затем обрабатываться одним или несколькими экземплярами отдельной службы (для целей масштабируемости). Эта служба может занять столько времени, сколько ей потребуется, чтобы обработать сообщение, и вы перемещаете параллелизм с клиента на сервер - нет необходимости использовать несколько потоков на клиенте, что означает более простой клиент.

Затем вы можете запросить состояние позже, опрашивая или отправляя сообщения обратно и т. Д. Тем временем вы можете пометить какое-то локальное состояние как «ожидающее» или что-то, чтобы оставить отзыв пользователю.

Идея 2

Если вызов веб-службы выходит из-под вашего контроля и занимает 2 минуты, значит, у вас проблемы. Вы можете написать свой собственный многопоточный код и обрабатывать уничтожающие потоки, когда приложение закрывается, но это плохая идея. Вы могли бы иметь локальную службу Windows как часть вашего клиентского приложения, которое работает все время, обрабатывая сообщения из очередей MSMQ на локальном компьютере. Тогда вы можете заставить это сервисное приложение вызывать долгосрочные методы веб-сервиса и при этом иметь клиентское приложение, которое быстро закрывается и отзывчиво?

Идея 3

У вас есть прокси-веб-служба, которую вы делаете управляете (то есть пишете сами), которая в основном выполняет то, что я сказал в идее 1 выше, но где «обработка сообщения» на самом деле вызывает длительный вебсервис.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...