Следует по возможности избегать прерывания и прерывания потоков, так как это может повредить состояние работающей программы.Например, представьте, что вы прервали поток, который держал блокировки открытыми для ресурсов, эти блокировки никогда не будут освобождены.
Вместо этого рассмотрите возможность использования механизма сигнализации, чтобы потоки могли взаимодействовать друг с другом и таким образом обрабатывать блокировки иИзящная разблокировка, например:
private readonly AutoResetEvent ProcessEvent = new AutoResetEvent(false);
private readonly AutoResetEvent WakeEvent = new AutoResetEvent(false);
public void Do()
{
Thread th1 = new Thread(ProcessSomething);
th1.IsBackground = false;
th1.Start();
ProcessEvent.WaitOne();
Console.WriteLine("Processing started...");
Thread th2 = new Thread(() => WakeEvent.Set());
th2.Start();
th1.Join();
Console.WriteLine("Joined");
}
private void ProcessSomething()
{
try
{
Console.WriteLine("Processing...");
ProcessEvent.Set();
}
finally
{
WakeEvent.WaitOne();
Console.WriteLine("Woken up...");
}
}
Обновление
Довольно интересная проблема низкого уровня.Хотя Abort()
задокументировано, Interrupt()
гораздо меньше.
Короткий ответ на ваш вопрос - нет, вы не можете разбудить поток в блоке finally, вызвав для него Abort
или Interrupt
.
Неспособность прервать или прервать потоки в блоках finally - это дизайн, просто так, чтобы блоки наконец могли работать так, как вы ожидаете.Если вы можете прервать и прервать потоки в блоках finally, это может привести к непредвиденным последствиям для процедур очистки, и, таким образом, оставить приложение в поврежденном состоянии - не очень хорошо.
Небольшой нюанс с прерыванием потока заключается в том, что прерывание можетбыли выданы потоку в любое время до того, как он вошел в блок finally, но пока он не находился в состоянии SleepWaitJoin
(т.е. не заблокирован).В этом случае, если бы был блокирующий вызов в блоке finally, он немедленно выбросил бы ThreadInterruptedException
и вылетел бы из блока finally.Наконец, защита блоков предотвращает это.
Наряду с защитой в блоках finally это распространяется и на пробные блоки, а также на CER ( Ограниченная область выполнения ), которые можно настроить в коде пользователя для предотвращения диапазона.исключений, генерируемых до тех пор, пока не будет выполнена область - очень полезно для критических блоков кода, которые должны быть завершенными и задержка прерывается.
Исключением (без каламбура) являются так называемые Rude Aborts .Они ThreadAbortExceptions
вызываются самой средой хостинга CLR.Это может привести к выходу из блоков finally и catch, но не CER.Например, CLR может вызывать Rude Aborts в ответ на потоки, которые, по его мнению, занимают слишком много времени для выполнения их работы \ выхода, например, при попытке выгрузить домен AppDomain или выполнение кода в CLR SQL Server.В вашем конкретном примере, когда ваше приложение закрывается и AppDomain выгружается, CLR выдаст Rude Abort в спящем потоке, так как будет тайм-аут выгрузки AppDomain.
Прерывание и прерывание в блоках finally не будут происходить в пользовательском коде, но между этими двумя случаями будет немного другое поведение.
Прервать
При вызове Abort
в потоке в блоке finally вызывающий поток блокируется.Это задокументировано :
Поток, вызывающий прерывание, может блокироваться, если прерываемый поток находится в защищенной области кода, такой как блок перехвата, блок finally,или ограниченная область выполнения.
В случае прерывания, если сон не был бесконечным:
- Вызывающий поток выдаст
Abort
, ноблокировать здесь до тех пор, пока не будет завершен блок finally, т.е. он останавливается здесь и не сразу переходит к оператору Join
. - Для потока вызываемого абонента установлено состояние
AbortRequested
. - Вызываемый абонент продолжает спать.
- Когда вызываемый абонент просыпается, он находится в состоянии
AbortRequested
он продолжит выполнение кода блока finally и затем «испарится», то есть завершит работу. - Когда прерванный поток покидает блок finally: исключение не возникает, код после блока finally не выполняется, и состояние потока равно
Aborted
. - Вызывающий поток разблокирован, продолжает выполнение оператора
Join
и сразу же проходит после выхода из вызываемого потока.
Итак, приведите ваш пример с бесконечным сномвызывающий поток заблокируется навсегда на шаге 1.
Прерывание
В случае прерывания, если режим сна не был бесконечным:
Не очень хорошо задокументировано ...
- Вызывающий поток выдаст
Interrupt
и continue при выполнении.
- Вызывающий поток заблокирует оператор
Join
.
- Поток вызываемого имеет свое состояние, установленное для вызова исключения при следующем вызове блокировки, но, что важно, поскольку он находится в блоке finally, он не разблокирован, то есть разбужен.
- Вызываемый продолжает спать.
- Когда вызываемый абонент проснется, он продолжит выполнение блока finally.
- Когда прерванный поток покидает блок finally, он скинет
ThreadInterruptedException
при следующем вызове блокировки (см. Пример кода ниже).
- Вызывающий поток «присоединяется» и продолжается после выхода из вызываемого потока, однако необработанный
ThreadInterruptedException
на шаге 6 теперь сгладил процесс ...
Итак, снова учитывая ваш пример с бесконечным сном, вызывающий поток будет блокироваться навсегда, но на шаге 2.
Краткое описание
Так что, хотя Abort
и Interrupt
ведут себя немного по-разному, они оба приведут к тому, что вызываемый поток спит вечно, а вызывающий поток навсегда блокируется (в вашем примере).
Только Rude Abort может заставить заблокированный поток выйти из блока finally, и он может быть вызван только самим CLR (вы даже не можете использовать отражение, чтобы перебрать ThreadAbortException.ExceptionState
, так как он делает внутренний вызов CLR, чтобы получить AbortReason
- там нет возможности быть легко злым ...).
CLR предотвращает преждевременный выход блоков наконец из кода пользователя для нашего же блага - это помогает предотвратить поврежденное состояние.
Для примера немного другого поведения с Interrupt
:
internal class ThreadInterruptFinally
{
public static void Do()
{
Thread t = new Thread(ProcessSomething) { IsBackground = false };
t.Start();
Thread.Sleep(500);
t.Interrupt();
t.Join();
}
private static void ProcessSomething()
{
try
{
Console.WriteLine("processing");
}
finally
{
Thread.Sleep(2 * 1000);
}
Console.WriteLine("Exited finally...");
Thread.Sleep(0); //<-- ThreadInterruptedException
}
}