Как я могу гарантировать быстрое завершение работы моего приложения win32?
У меня есть приложение C32 Win32, в котором есть несколько потоков, которые могут быть заняты выполнением операций ввода-вывода (HTTP-вызовы и т. Д.), Когда пользователь хочет закрыть приложение. В настоящее время я играю хорошо и жду окончания всех потоков, прежде чем вернуться из main
, Иногда это занимает больше времени, чем мне бы хотелось, и, действительно, кажется бессмысленным заставлять пользователя ждать, когда я смогу просто выйти. Однако, если я просто продолжу и вернусь из main
Я, скорее всего, получу сбои, когда деструкторы начинают вызывать, пока еще есть потоки, использующие объекты.
Итак, признавая, что в идеальном платоническом мире добродетели лучше всего подождать, пока все потоки выйдут, а затем аккуратно завершить работу, каково следующее лучшее решение в реальном мире? Простое ускорение выхода из потоков может быть невозможным. Цель состоит в том, чтобы как можно быстрее остановить процесс, чтобы, например, поверх него можно было установить новую версию. Единственный дисковый ввод-вывод, который я делаю, находится в транзакционной базе данных, так что я не очень беспокоюсь о том, чтобы отключить его.
11 ответов
Используйте перекрывающийся ввод-вывод, чтобы вы всегда контролировали потоки, связанные с вашим вводом-выводом, и всегда могли остановить их в любой момент; вы либо ожидаете их на IOCP и можете опубликовать в нем код завершения работы уровня приложения, либо вы можете дождаться события в вашей структуре OVERLAPPED и дождаться события "все потоки, пожалуйста, завершите сейчас".
Таким образом, избегайте блокирования вызовов, которые нельзя отменить.
Если вы не можете и застряли в вызове блокирующего сокета, выполняя ввод-вывод, вы всегда можете просто закрыть сокет из потока, который решил, что пора завершать работу, и чтобы поток, выполняющий ввод-вывод, всегда проверял "выключение сейчас". 'событие, прежде чем повторить попытку...
Я использую технику на основе исключений, которая довольно хорошо работала для меня в ряде приложений Win32.
Чтобы прекратить поток, я использую QueueUserAPC()
поставить в очередь вызов функции, которая выдает исключение. Однако выбрасываемое исключение не является производным от типа "Исключение", поэтому будет перехватываться только процедурой-оболочкой моего потока.
Преимущества этого заключаются в следующем:
- В вашем потоке не требуется специального кода, чтобы сделать его "остановимым" - как только он войдет в состояние ожидания с оповещением, он запустит функцию APC.
- Все деструкторы вызываются, поскольку исключение запускает стек, поэтому ваш поток завершается корректно.
Вещи, за которыми нужно следить:
- Что-нибудь делает
catch (...)
съест ваше исключение. Код пользователя должен всегда использоватьcatch(const Exception &e)
или похожие! - Убедитесь, что ваш ввод / вывод и задержки выполняются "оповещаемым" способом. Например, это означает, что
sleepex(N, true)
вместоsleep(N)
, - Связанные с процессором потоки должны вызывать
sleepex(0,true)
иногда проверять на прекращение.
Вы также можете "защитить" области своего кода, чтобы предотвратить завершение задачи во время критических разделов.
Я бы порекомендовал иметь ваш графический интерфейс и работать над разными потоками. Когда пользователь запрашивает завершение работы, немедленно закрывайте графический интерфейс, создавая впечатление, что приложение закрыто. Позвольте рабочим потокам изящно закрыться в фоновом режиме.
Лучший способ: выполняйте свою работу во время работы приложения и ничего не делайте (или как можно ближе к нему) при завершении работы (работает и при запуске). Если вы придерживаетесь этого паттерна, то можете сразу же разорвать потоки (вместо того, чтобы "быть вежливыми" по этому поводу), когда приходит запрос на отключение, не беспокоясь о работе, которую еще нужно сделать.
В вашей конкретной ситуации вам, вероятно, придется ждать завершения ввода-вывода (пишет, по крайней мере), если вы выполняете там локальную работу. HTTP-запросы и тому подобное, вы, вероятно, можете просто отказаться / закрыть сразу (опять же, если вы что-то не пишете). Но если это тот случай, когда вы пишете во время этого завершения и ждете этого, вы можете уведомить пользователя об этом, вместо того, чтобы ваш процесс выглядел зависшим во время завершения работы.
Если вы хотите беспрепятственно вытащить пробку, выход (0) сделает свое дело.
Если вам нужно внезапно завершить работу: просто вызовите ExitProcess, который будет вызываться, как только вы все равно вернетесь из WinMain. Сама Windows создает много рабочих потоков, которые не могут быть очищены - они прекращаются при завершении процесса.
Если у вас есть какие-либо потоки, которые выполняют записи какого-либо типа - очевидно, что им нужен шанс закрыть свои ресурсы. Но все остальное - игнорируйте предупреждения проверки границ и просто вытащите коврик из-под ног.
Вы можете позвонить в TerminateProcess - это немедленно остановит процесс, не уведомляя никого и ничего не ожидая.
*NULL = 0
это самый быстрый способ. если вы не хотите сбой, позвоните exit()
или его эквивалент win32.
Однажды у меня была похожая проблема, хотя и в Visual Basic 6: потоки из приложения могли подключаться к разным серверам, загружать некоторые данные, выполнять некоторые операции с этими циклами и сохранять результат на централизованном сервере.
Затем новым требованием было прекращение потоков из основной формы. Я выполнил это простым, хотя и грязным способом, остановив потоки после N циклов (эквивалентно примерно полсекунде), чтобы попытаться открыть мьютекс с определенным именем. После успеха они немедленно прекратили все, что они делали, и ушли, продолжили иначе.
Этот мьютекс был создан только основной формой, после его создания все потоки вскоре закроются. Недостатком было то, что пользователю нужно было вручную указать, что он хочет снова запустить потоки - еще одна кнопка "Включить потоки для запуска" сделала это, освободив мьютекс:D
Этот трюк гарантированно работает, поскольку мьютексные операции являются атомарными. Проблема в том, что вы никогда не уверены, что поток действительно закрыт - сбой в логике обработки случая "openMutex преуспел" может означать, что он никогда не закончится. Вы также не знаете, когда / если все потоки закрылись (при условии, что ваш код прав, это займет примерно столько же времени, сколько потребуется циклам для остановки и "прослушивания").
С моделью многопоточности VB "квартира" несколько трудно посылать информацию из потоков в основное приложение туда и обратно, гораздо проще "запускать и забывать" или отправлять ее только из основного приложения в поток. Таким образом, необходимость такого рода сокращений. Используя C++, вы можете свободно использовать свою многопоточную модель, поэтому эти ограничения могут на вас не распространяться.
Что бы вы ни делали, НЕ используйте TerminateThread, особенно для всего, что может быть в вызовах ОС HTTP. Вы можете потенциально сломать IE до перезагрузки.
Измените все свои операции ввода-вывода на асинхронную или неблокирующую модель, чтобы они могли отслеживать события завершения.
Попросите пользователя отключить компьютер. Если не считать этого, вы должны отказаться от своей асинхронной деятельности по ветру. Или это ХВИНД? Я никогда не могу вспомнить в C++. Конечно, вы можете выбрать среднюю дорогу и быстро указать в текстовом файле или указать, какое действие было прервано, чтобы при следующем запуске программы оно могло автоматически выполнить это действие снова или спросить пользователя, хотят ли они это сделать. В зависимости от того, какие данные вы потеряете при отказе от асинхронного действия, вы не сможете сделать это. Если вы взаимодействуете с пользователем, возможно, вы захотите рассмотреть диалог или какое-либо взаимодействие с пользовательским интерфейсом, которое объясняет, почему это занимает так много времени.
Лично я предпочитаю инструкцию для пользователя, чтобы просто отключить компьютер.:)