Почему поток не прерывается при сне в блоке finally

Я искал по всему MSDN и не могу найти причину, по которой поток не может быть прерван во время сна в блоке 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=против 71).aspx "Есть шанс, что нить может прерваться, пока блок finally выполняется, в этом случае блок finally не выполняется."

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

2 ответов


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

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

    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, это может иметь непреднамеренные последствия для процедур очистки, и поэтому оставить приложение в поврежденном состоянии-нехорошо.

небольшой нюанс с прерыванием потока заключается в том, что прерывание может быть выдано против потока в любое время, прежде чем он вошел в блок finally, но пока он не был в SleepWaitJoin состояние (т. е. не заблокировано). В этом случае, если бы был блокировочный вызов в блоке finally, он немедленно бросил бы ThreadInterruptedException и вылететь из блока finally. Окончательно предохранение от Блока предотвращает этот.

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

исключение (без каламбура) для этого-то, что называется Грубо Прерывает. Это ThreadAbortExceptions поднятый самой средой хостинга CLR. Это может привести, наконец, и поймать блоки для выхода, но не ССВ. Например, среда CLR может вызывать грубые прерывания в ответ на потоки, которые, по ее мнению, слишком долго выполняют свою работу\exit, например, при попытке выгрузить AppDomain или выполнить код в среде SQL Server CLR. В вашем конкретном примере, когда ваше приложение завершает работу и AppDomain выгружается, среда CLR выдаст грубый прерывание сна поток, поскольку будет тайм-аут разгрузки AppDomain.

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

отмена

при вызове Abort в потоке в блоке finally вызывающий поток блокируется. Это документирована:

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

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

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

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

отмена

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

не так хорошо документированы...

  1. вызывающий поток выдаст Interrupt и дальше выполнения.
  2. вызывающий поток будет блокировать на Join заявление.
  3. вызываемый поток имеет свое состояние, установленное для вызова исключения при следующем вызове блокировки, но принципиально, поскольку он находится в блоке finally, он не разблокирован, т. е. пробужден.
  4. вызываемый продолжает спать.
  5. когда вызываемый абонент просыпается,он продолжит выполнение блока finally.
  6. когда прерванный поток покидает блок finally, он будет бросать ThreadInterruptedException при следующем вызове блокировки (см. Пример кода ниже).
  7. в вызывающий поток "присоединяется" и продолжается, поскольку вызванный поток вышел, однако необработанный ThreadInterruptedException на шаге 6 теперь сгладил процесс...

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

резюме

хотя Abort и Interrupt имеют немного другое поведение, они оба приведут к вызываемому потоку, спящему навсегда, и вызывающему потоку, блокирующему навсегда (в ваш пример.)

только грубый аборт может заставить заблокированный поток выйти из блока finally, и они могут быть подняты только самим CLR (вы даже не можете использовать отражение для diddle ThreadAbortException.ExceptionState как он делает внутренний вызов CLR, чтобы получить AbortReason - нет возможности быть легко злым там...).

CLR предотвращает код пользователя от причинения блоков finally преждевременно выходить для нашего собственного блага-это помогает предотвратить поврежденное состояние.

для пример немного другого поведения с 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
    }
}   

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

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