Можно ли убить WaitForSingleObject (handle, INFINITE)? - PullRequest
7 голосов
/ 17 мая 2011

У меня проблемы с закрытием приложения, которое использует WaitForSingleObject () с тайм-аутом INFINITE.

Полная картина такова.Я делаю следующее, чтобы приложение могло обработать событие активации устройства:

Зарегистрируйте событие с помощью:

CeRunAppAtEvent("\\\\.\\Notifications\\NamedEvents\\WakeupEvent",
    NOTIFICATION_EVENT_WAKEUP);

Запустите новый поток для ожидания:

Thread waitForWakeThread = new Thread(new ThreadStart(WaitForWakeup));
waitForWakeThread.Start();

Затем выполните следующий метод в целевом методе:

private void WaitForWakeup()
{
    IntPtr handle = CreateEvent(IntPtr.Zero, 0, 0, "WakeupEvent");
    while (true)
    {
        WaitForSingleObject(handle, INFINITE);
        MessageBox.Show("Wakey wakey");
    }
}

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

Есть ли способ убить дескриптор, которого ожидает WaitForSingleObject, и заставить его вернуться?

Большое спасибо.

Ответы [ 4 ]

18 голосов
/ 17 мая 2011

Используйте взамен WaitForMultipleObject и передайте 2 дескриптора. Существующий и один для события, называемого «выход». Во время закрытия приложения SetEvent в событии exit и WaitForMultipleObject вернутся, и вы можете получить его для корректного выхода из потока.

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

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

См:

http://msdn.microsoft.com/en-us/library/system.threading.thread.isbackground.aspx

1 голос
/ 18 мая 2011

'Это все работает нормально, пока я не попытаюсь закрыть приложение, когда, как и ожидалось, WaitForSingleObject продолжает ждать и не позволяет приложению закрыться должным образом.'

Любое приложение может закрыться, независимо от того, какие у него потокиделают.Если вы вызываете ExitProcess (0) из какого-либо потока в вашем приложении, приложение закроется, независимо от того, есть ли потоки, ожидающие INFINITE на каком-то API / sychro, спящие, работающие на другом процессоре, что угодно.ОС изменит состояние всех неиспользуемых команд, чтобы «никогда не запускаться снова», и использует свой межпроцессорный драйвер для жесткого прерывания любых других процессоров, которые фактически выполняют код вашего потока.Как только все потоки остановлены, ОС освобождает дескрипторы, сегменты и т. Д., И ваше приложение больше не существует.

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

У вас есть TThread.WaitFor или аналогичный в обработчике OnClose / OnCloseQuery, FormDestroy или деструкторе?Если у вас есть и у вас нет веских причин гарантировать, что поток завершен, просто закомментируйте его!

Это позволяет закрывать основную форму, и, таким образом, ваш код, наконец, достигает ExitProcess (), который он пыталсячтобы получить, так как вы нажали на кнопку красного креста

Конечно, вы могли бы просто вызвать ExitProcess () самостоятельно, но это может привести к утечке ресурсов в других процессах - например, соединения с базой данных.

'Ошибки 216/217 при закрытии, если я не остановлю потоки'.Это часто происходит потому, что разработчики следовали ошибочным «неудачным» примерам потоков Delphi и общаются с потоками, напрямую обмениваясь данными между полями вторичных потоков и полями основных потоков (например, TThread.synchronize).Это просто отстой, и он одержим проблемами, даже при запуске приложения, не говоря уже о закрытии, когда форма уничтожена, и поток пытается записать в нее, или поток был уничтожен, и форма основного потокапытаясь не вызывать методы на нем.Гораздо безопаснее общаться асинхронно с потоками с помощью объектов очередей / PostMessaging, которые переживают их обоих, например.объекты, созданные в потоке / форме и освобожденные в форме / потоке или с помощью (потокобезопасного) пула объектов, созданных в разделе инициализации.Затем формы можно безопасно закрывать / освобождать, в то время как связанные потоки могут продолжать бессмысленно заполнять объекты для обработки до тех пор, пока не закроется основная форма, не будет достигнут ExitProcess () и ОС не уничтожит потоки.

'Дескриптор моей формы недопустим, посколькуон закрылся, но моя ветка пытается отправить ему сообщение ».Если PostMessage исключает, выйдите из вашей темы.Лучший способ аналогичен подходу, описанному выше - отправлять сообщения только в окно, которое переживает все формы.Создайте его в разделе инициализации с помощью тривиального WndProc, который обрабатывает только один постоянный номер сообщения, который все потоки используют для публикации.Вы можете использовать wParam для передачи экземпляра TwinControl , с которым пытается связаться поток (обычно это переменная формы), в то время как lParam передает передаваемый объект.Когда он получает сообщение из потока, WndProc вызывает 'Peform' на переданном TwinControl, и TwinControl получает объект comms в обработчике сообщений.Простое глобальное логическое значение, AppClosing, скажем, может остановить WndProc, вызывающий Peform () для TwinControls, которые освобождаются во время завершения работы.Этот подход также позволяет избежать проблем, возникающих, когда ОС воссоздает ваше окно формы с другим дескриптором - дескриптор формы Delphi не используется, и Windows не будет повторно создавать / изменять дескриптор простой формы, созданной при инициализации.

У меня естьследовал этим подходам в течение десятилетий и не испытывал никаких проблем с завершением работы, даже если приложения с десятками потоков перебрасывали объекты в очереди.

Rgds, Martin

1 голос
/ 17 мая 2011

Это то, что я бы сделал ...

  1. Используйте класс EventWaitHandle вместо прямого вызова CreateEvent.Не должно быть никакой необходимости использовать Windows API, кроме CeRunAppAtEvent (а вызовы API делают код уродливым ...).Получите это в первую очередь.
  2. Перед созданием потока создайте переменную ManualResetEvent, которая изначально не была помечена.Назовите его «TerminateEvent».
  3. Замените API-вызов WaitForSingleObject на WaitHandle.WaitAny (WaitHandle []) и передайте массив, содержащий «TerminateEvent» и класс EventWaitHandle, окружающий уведомление CeRunAppAtEvent.
  4. Цикл может использовать возвращаемое значение WaitAny, чтобы определить, что делать.Возвращаемое значение является индексом массива дескриптора ожидания, который разблокировал поток, поэтому вы можете определить, следует ли продолжать цикл или нет.
  5. Чтобы выполнить чистый конец потока, вы можете вызвать «Set» на вашем «TerminateEvent»."и затем" Присоединиться "к потоку, чтобы дождаться его завершения.
0 голосов
/ 17 мая 2011

Конечно, предпочтительный способ решить эту проблему - использовать WaitForMultipleObjects или любую другую подходящую функцию, которая может ожидать несколько критериев (например, WaitForMultipleObjects, MsgWaitForMultipleObjects и т.

Однако, если у вас нет контроля над тем, какая функция используется - есть несколько хитрых способов решить эту проблему. Вы можете hack импортировать функции из системной DLL, изменяя в памяти таблицу импорта любого модуля. Поскольку WaitForMultipleObjects экспортируется из kernel32.dll - это нормально. используя эту технику, вы можете перенаправить вызывающего функцию в свои руки, и там вы сможете использовать WaitForMultipleObjects.

...