Руководство по отказу от прерывания потока является спорным. Я думаю, что для этого еще есть место, но в исключительных обстоятельствах. Тем не менее, вы всегда должны пытаться придумать и рассмотреть это как последнее средство.
Пример;
У вас есть простое приложение Windows Form, которое подключается к блокирующему синхронному веб-сервису. В рамках которого он выполняет функцию веб-службы в параллельном цикле.
CancellationTokenSource cts = new CancellationTokenSource();
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
Parallel.ForEach(iListOfItems, po, (item, loopState) =>
{
Thread.Sleep(120000); // pretend web service call
});
Скажем, в этом примере блокирующий вызов занимает 2 минуты. Теперь я установил свой MaxDegreeOfParallelism, чтобы сказать ProcessorCount. В iListOfItems есть 1000 элементов для обработки.
Пользователь нажимает кнопку процесса, и цикл начинается, у нас есть «до» 20 потоков, выполняющих 1000 элементов в коллекции iListOfItems. Каждая итерация выполняется в своем собственном потоке. Каждый поток будет использовать поток переднего плана при создании Parallel.ForEach. Это означает, что независимо от закрытия основного приложения, домен приложения будет оставаться активным до тех пор, пока не завершатся все потоки.
Однако пользователю по какой-то причине необходимо закрыть приложение, например, закрыть форму.
Эти 20 потоков будут выполняться до тех пор, пока не будут обработаны все 1000 элементов. Это не идеальный вариант в этом сценарии, поскольку приложение не будет закрываться, как того ожидает пользователь, и будет продолжать работать за кулисами, что можно увидеть, заглянув в диспетчер задач.
Скажем, пользователь пытается перестроить приложение еще раз (VS 2010), он сообщает, что исполняемый файл заблокирован, затем ему нужно будет зайти в диспетчер задач, чтобы убить его, или просто подождать, пока все 1000 элементов будут обработаны.
Я бы не стал винить вас за это, но, конечно же! Я должен был отменить эти потоки, используя объект CancellationTokenSource и вызывая Cancel ..., но с .net 4.0 есть некоторые проблемы с этим. Во-первых, это по-прежнему никогда не приведет к прерыванию потока, которое вызовет исключение прерывания с последующим завершением потока, поэтому домену приложения вместо этого придется ждать нормального завершения потоков, а это означает ожидание последнего вызова блокировки, это будет самая последняя запущенная итерация (поток), которая в конечном итоге вызовет po.CancellationToken.ThrowIfCancellationRequested
.
В этом примере это будет означать, что домен приложения может оставаться активным до 2 минут, даже если форма была закрыта и вызвана отмена.
Обратите внимание, что вызов Cancel в CancellationTokenSource не генерирует исключение в потоке (ах) обработки, которое действительно будет действовать, чтобы прервать блокирующий вызов, аналогичный прерыванию потока, и остановить выполнение. Исключение кэшируется, готово к тому моменту, когда все другие потоки (параллельные итерации) в конце концов завершаются и возвращаются, исключение выдается в инициирующем потоке (где объявлен цикл).
Я выбрал , а не , чтобы использовать Cancel для объекта CancellationTokenSource. Это расточительно и, возможно, нарушает хорошо известный антипаттен управления потоком кода с помощью исключений.
Вместо этого, возможно, «лучше» реализовать простое поточно-ориентированное свойство, то есть Bool stopExecuting. Затем в цикле проверьте значение stopExecuting и, если внешнее воздействие установит значение true, мы можем выбрать альтернативный путь, чтобы изящно закрыться. Поскольку мы не должны вызывать отмену, это исключает проверку CancellationTokenSource.IsCancellationRequested , которая в противном случае была бы другой опцией.
Что-то похожее на следующее, если условие будет подходящим в цикле;
if (loopState.ShouldExitCurrentIteration || loopState.IsExceptional || stopExecuting) {loopState.Stop (); возвращение;}
Итерация теперь будет выходить «управляемым» способом, а также завершать дальнейшие итерации, но, как я уже сказал, это мало что дает для нашей проблемы необходимости ждать продолжительного и блокирующего вызова (вызовов), выполняемого внутрикаждая итерация (поток параллельного цикла), так как они должны завершиться, прежде чем каждый поток сможет получить возможность проверить, должен ли он остановиться.
В итоге, когда пользователь закрывает форму, 20 потокам будет дано предупреждение об остановке через stopExecuting, но они остановятся только после того, как завершат выполнение своего долгосрочного вызова функции.
Мыничего не может поделать с тем фактом, что домен приложения всегда будет оставаться живым и освобождаться только после завершения всех потоков переднего плана.А это означает, что будет задержка, связанная с ожиданием завершения любых блокирующих вызовов, выполненных в цикле.
Только истинный прерывание потока может прервать блокирующий вызов, и вы должны смягчить, оставив систему в нестабильном / неопределенном состоянии, как можно лучше в обработчике исключений прерванного потока, который не вызывает вопросов.Вопрос о том, является ли это уместным, определяется программистом на основе того, какие дескрипторы ресурсов он выбрал для обслуживания, и насколько легко закрыть их в блоке finally потока.Вы можете зарегистрироваться с помощью токена, чтобы завершить работу при отмене как полуобходное решение, например
CancellationTokenSource cts = new CancellationTokenSource();
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
Parallel.ForEach(iListOfItems, po, (item, loopState) =>
{
using (cts.Token.Register(Thread.CurrentThread.Abort))
{
Try
{
Thread.Sleep(120000); // pretend web service call
}
Catch(ThreadAbortException ex)
{
// log etc.
}
Finally
{
// clean up here
}
}
});
, но это все равно приведет к исключению в объявленном потоке.
Учитывая все вышесказанное, вызовы блокировки прерываний с использованием конструкций parallel.loop могли бы быть методом опций, избегая использования более неясных частей библиотеки.Но почему нет возможности отменить и избежать выбрасывания исключения в методе объявления, мне кажется возможным упущением.