Почему код в блоке finally не выполняется? - PullRequest
8 голосов
/ 13 марта 2012

Кажется, что блок finally не выполняется, если он отличается от кода выполнения основного потока. Возможно ли принудительное выполнение в этом случае?

Среда: VS 2010, .Net Framework 4.0.3

class Program
{
    static void Main(string[] args)
    {
        var h = new AutoResetEvent(false);

        ThreadPool.QueueUserWorkItem(
            obj => TestProc(h));

        h.WaitOne();
    }

    private static void TestProc(EventWaitHandle h)
    {
        try
        {
            Trace.WriteLine("Try");
            h.Set();
        }
        catch(Exception)
        {
            Trace.WriteLine("Catch");
        }
        finally
        {
            Thread.Sleep(2000);
            Trace.WriteLine("Finally");
        }
    }
}

Обновление:

Я нашел упоминания и объяснения по этому делу в MSDN:

ThreadAbortException Class http://msdn.microsoft.com/en-us/library/system.threading.threadabortexception.aspx

Когда вызывается метод Abort для уничтожения потока, общеязыковая среда выполнения выдает исключение ThreadAbortException. ThreadAbortException - это особое исключение, которое может быть перехвачено, но оно будет автоматически поднят снова в конце блока захвата. когда это исключение возникает, среда выполнения выполняет все блоки finally до окончания потока . Потому что поток может делать неограниченное вычисление в блоках finally или вызов Thread.ResetAbort для отмены прерывание, нет никакой гарантии, что поток когда-либо закончится . Если Вы хотите подождать, пока прерванный поток не закончится, вы можете вызвать Thread.Join метод. Присоединение - это блокирующий вызов, который не возвращается до поток фактически останавливается.

Примечание:

Когда общеязыковая среда выполнения (CLR) останавливает фоновые потоки после все потоки переднего плана в управляемом исполняемом файле закончились, это не использовать Thread.Abort. Следовательно, вы не можете использовать ThreadAbortException для определить, когда фоновые потоки завершаются CLR.


Темы переднего плана и фона http://msdn.microsoft.com/en-us/library/h339syd0.aspx

Когда среда выполнения останавливает фоновый поток из-за завершения процесса, исключение не генерируется в потоке. Однако, когда потоки останавливаются из-за того, что метод AppDomain.Unload выгружает домен приложения, ThreadAbortException генерируется как в основном, так и в фоновом потоках.


Так почему же в конце приложения CLR не использует метод AppDomain.Unload для выгрузки домена приложения до завершения (уничтожения) основного процесса? Потому что http://msdn.microsoft.com/en-us/library/system.appdomain.unload.aspx:

Когда поток вызывает Unload, целевой домен помечается для выгрузки. Выделенный поток пытается выгрузить домен и все потоки в домен прерван. Если поток не прерывается, например потому что он выполняет неуправляемый код, или потому что он выполняет наконец, блок, затем через некоторое время CannotUnloadAppDomainException выбрасывается в поток, который первоначально называется Unload. Если поток, который не может быть прерван в конце концов заканчивается, целевой домен не выгружается. Таким образом, в .NET Framework домен версии 2.0 не гарантированно выгружает , потому что он может не можно прекратить выполнение потоков.

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

Ответы [ 2 ]

17 голосов
/ 13 марта 2012

Ваш код выполняется в фоновом потоке.Когда вы устанавливаете AutoResetEvent, ваш единственный поток переднего плана завершается (когда вы достигаете конца метода Main), и процесс прерывается «немедленно».

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

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

4 голосов
/ 13 марта 2012

Вы можете запретить выход основного метода до тех пор, пока, наконец, не выполнится. Есть много возможных подходов.

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

  • Вы можете просто спать или читать строку в конце метода Main:

    h.WaitOne();
    Console.ReadLine();
    

Затем пользователь может контролировать выход из программы.

  • Вы можете использовать не фоновый поток вместо потока из пула потоков. Тогда программа не выйдет, пока поток не завершится. Это, вероятно, лучший и самый простой вариант, если вы хотите, чтобы ваша программа не прерывалась до тех пор, пока поток не завершится.
...