Почему поток не прерывается, когда спит в блоке finally - PullRequest
9 голосов
/ 15 августа 2011

Я искал по всему MSDN и не могу найти причину, по которой Thread не может быть прерван во время сна в блоке finally.Я попытался прервать безуспешно.

Есть ли какой-нибудь способ как разбудить поток, когда он спит в блоке finally?

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
    {
        try
        {
            Thread.Sleep(Timeout.Infinite);
        }
        catch (ThreadInterruptedException ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

Удивительно, но MSDN утверждает, что поток МОЖЕТ быть прерван в блоке finally: http://msdn.microsoft.com/en-us/library/aa332364(v=vs.71).aspx "Существует вероятность того, что поток может быть прерван во время выполнения блока finally, и в этом случае блок finally отменяется."

edit Я нахожу комментарий Ганса Пассанта каклучший ответ, поскольку это объясняет, почему поток иногда может и не может быть прерван / прерван в блоке finally.И тогда процесс останавливается.спасибо

Ответы [ 2 ]

9 голосов
/ 15 августа 2011

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

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

    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,или ограниченная область выполнения.

В случае прерывания, если сон не был бесконечным:

  1. Вызывающий поток выдаст Abort, ноблокировать здесь до тех пор, пока не будет завершен блок finally, т.е. он останавливается здесь и не сразу переходит к оператору Join.
  2. Для потока вызываемого абонента установлено состояние AbortRequested.
  3. Вызываемый абонент продолжает спать.
  4. Когда вызываемый абонент просыпается, он находится в состоянии AbortRequestedон продолжит выполнение кода блока finally и затем «испарится», то есть завершит работу.
  5. Когда прерванный поток покидает блок finally: исключение не возникает, код после блока finally не выполняется, и состояние потока равноAborted.
  6. Вызывающий поток разблокирован, продолжает выполнение оператора Join и сразу же проходит после выхода из вызываемого потока.

Итак, приведите ваш пример с бесконечным сномвызывающий поток заблокируется навсегда на шаге 1.

Прерывание

В случае прерывания, если режим сна не был бесконечным:

Не очень хорошо задокументировано ...

  1. Вызывающий поток выдаст Interrupt и continue при выполнении.
  2. Вызывающий поток заблокирует оператор Join.
  3. Поток вызываемого имеет свое состояние, установленное для вызова исключения при следующем вызове блокировки, но, что важно, поскольку он находится в блоке finally, он не разблокирован, то есть разбужен.
  4. Вызываемый продолжает спать.
  5. Когда вызываемый абонент проснется, он продолжит выполнение блока finally.
  6. Когда прерванный поток покидает блок finally, он скинет ThreadInterruptedException при следующем вызове блокировки (см. Пример кода ниже).
  7. Вызывающий поток «присоединяется» и продолжается после выхода из вызываемого потока, однако необработанный 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
    }
}   
4 голосов
/ 15 августа 2011

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

Вместо этого используйте совместный дизайн.Если предполагается, что поток прерывается, вместо вызова Sleep, используйте временное ожидание.Вместо вызова Interrupt сигнализирует о том, чего ожидает поток.

...