Поток сохраняется после завершения приложения из-за сигнала AutoResetEvent в состоянии WaitOne

У меня есть приложение, которое использует AutoResetEvent (WaitOne/Set) в очереди для обработки сообщений. Я заметил, что когда я завершаю сеанс отладки из Visual Studio (Shift+F5), исходный процесс для приложения зависает (но не всегда). Я вручную повторно подключаю отладчик к процессу и вижу, что один поток застрял в WaitHandle.WaitOne.

Итак, мой вопрос: как правильно завершать потоки, которые могут находиться в состоянии WaitOne?

Первый ответ, который пришел мне в голову, заключался в том, чтобы прослушать событие Application Exit и выполнить там Set, но я не был уверен, было ли это событие надежно вызвано после этих сеансов отладки или существует более стандартная практика, о которой я не знаю. из.

И, в качестве второго вопроса, вы бы справились с этим по-другому для приложения, работающего в «производственном» режиме?


person Matt    schedule 08.07.2011    source источник
comment
Хм, у вас есть неприятная проблема на ваших руках. Завершение сеанса отладки выполняется с помощью самой мощной пушки, доступной в Windows, TerminateProcess(). Такой же пистолет используется в Taskmgr.exe. Существует только один возможный способ, которым это не завершает процесс: поток ядра занят и не выполняет запрос ввода-вывода. Это определенно не AutoResetEvent. Не имеет смысла.   -  person Hans Passant    schedule 09.07.2011
comment
@Hans - значит, WaitHandle, который я вижу в стеке вызовов после подключения отладчика, является не дескриптором ожидания для сигнала, а другим дескриптором ожидания для задачи ввода-вывода ядра? Единственным вводом-выводом в этой задаче является некоторая регистрация, и это действительно довольно прямолинейно (и только время от времени запускается вручную, а не что-то происходит с какой-либо частотой)   -  person Matt    schedule 10.07.2011
comment
Все, что я знаю, это то, что то, что вы описываете, не имеет смысла. Поэтому без какой-либо документации я с готовностью предполагаю, что описание неверно. Опубликуйте трассировку стека с включенной неуправляемой отладкой и серверами символов Microsoft.   -  person Hans Passant    schedule 10.07.2011
comment
@ Ганс, спасибо за предложение, сделаю это, когда вернусь в офис и получу код под рукой. Я ценю руководство.   -  person Matt    schedule 10.07.2011
comment
Я только что понял, что на самом деле у меня есть еще одна операция ввода-вывода. Мой уставший мозг совершенно забыл о читаемом TcpStream, и, если я правильно помню, когда я взглянул на компонент, который я использую, он делает блокирующий вызов Read в потоке. Вооружившись этим пониманием и вашими предложениями по более сложной отладке, я надеюсь решить проблему в понедельник.   -  person Matt    schedule 10.07.2011
comment
Конечно, я еще не видел такого поведения на этой неделе...   -  person Matt    schedule 13.07.2011


Ответы (2)


Есть простой способ сделать это (не обходной путь)

Во-первых, вам нужно установить событие, которое сработает, когда ваше приложение умрет.

// somewhere with global scope. On a singleton or in program class maybe
// this is set when you want to terminate your application
private static ManualResetEvent ExitWaitHandle = new ManualResetEvent(false);

И вот как использовать его в другом месте

// the event you want to check but it's blocking your application termination
private static AutoResetEvent yourEvent = new AutoResetEvent(true);

// the method where you have the problem
private static void FooAsync()
{
    try
    {
        WaitHandle.WaitAny(new WaitHandle[]{yourEvent, ExitWaitHandle});
        Checkpoint();

        // other stuff here

        // check if thread must die
        Checkpoint();
    }
    catch(ApplicationTerminatingException)
    {
        // thread must die, do cleanup and finalization stuff here
    }
    catch(Exception)
    {
        // holy cow! what should we do?
    }
}

private void CheckPoint()
{
    // fast check if the exit handle is set
    if(ExitWaitHandle.WaitOne(0))
    {
        throw new ApplicationTerminatingException(); // custom exception
    }
}

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

person Odys    schedule 30.11.2012
comment
это очень интересный ответ. Я попробую это первым делом завтра и дам вам знать, как это идет. Спасибо. - person Matt; 03.12.2012
comment
Теперь, когда передо мной мой код, я снова смотрю на это, и у меня есть пара вопросов. 1) Я предполагаю, что ваша «глобальная» область действия ExitWaitHandle должна быть общедоступной? и 2) Является ли основная идея здесь в том, что вы открываете «ворота» для сигнала через ExitWaitHandle во время процесса выключения, тем самым освобождая любые дескрипторы ожидания и позволяя им распоряжаться собой? - person Matt; 03.12.2012
comment
@Matt Да, ты открываешь ворота! Что касается события, оно не должно быть общедоступным, но доступным для всех классов, которым необходимо прослушивать событие завершения приложения. Вы можете реализовать шаблон наблюдателя, в котором подписчиками являются потоки. И небольшой комментарий нацистского кода: я предпочитаю ключевое слово internal вместо public для таких вещей. - person Odys; 04.12.2012
comment
Раннее тестирование, кажется, предполагает, что это работает для меня. Спасибо за помощь в этой давно нерешенной проблеме. - person Matt; 04.12.2012

Одним из решений является установка потока в качестве фонового потока с помощью Thread.IsBackground. При установке в потоке этот поток не остановит процесс для выхода.

Однако поток может быть прерван в любое время, что обычно приводит к неопределенному поведению в зависимости от того, что делает ваш поток. По моему скромному мнению, лучший способ завершить поток - это сигнализировать потоку о выходе, например. установив флаг выхода и установив WaitHandle и разбудив его, а затем Join в потоке.

person vidstige    schedule 08.07.2011
comment
Потоки фактически обрабатываются через объект Task .NET 4. Я полагаю, что я мог бы сделать некоторые ручные потоки ... - person Matt; 10.07.2011