В моем коде (сложное приложение с графическим интерфейсом с Tkinter) у меня есть поток, определенный в пользовательском объекте (индикатор выполнения). Он запускает функцию с циклом while следующим образом:
def Start(self):
while self.is_active==True:
do it..
time.sleep(1)
do it..
time.sleep(1)
def Stop(self):
self.is_active=False
Он может завершиться только тогда, когда другой фрагмент кода, размещенный в другом потоке, изменит атрибут self.is_active с помощью метода self.Stop(). У меня такая же ситуация в другом пользовательском объекте (счетчик), и они оба должны работать вместе, когда работает другой поток (основной).
Код работает, но я понял, что два потока, связанные с индикатором выполнения и счетчиком, не завершаются мгновенно, как я хотел, потому что, прежде чем завершаться, им нужно дождаться окончания своих функций, а эти медленные из-за инструкции time.sleep(1). С точки зрения пользователя это означает видеть конец основного потока с индикатором выполнения и счетчиком, который завершается ПОЗЖЕ, и мне это не нравится.
Честно говоря, я не знаю, как решить эту проблему. Есть ли способ заставить поток немедленно завершиться, не дожидаясь окончания функции?
Самый безопасный способ сделать это, не рискуя ошибкой сегментации, — вернуться.
def Start(self):
while self.is_active==True:
do it..
if not self.is_active: return
time.sleep(1)
if not self.is_active: return
do it..
if not self.is_active: return
time.sleep(1)
def Stop(self):
self.is_active=False
Потоки python должны освобождать связанные ресурсы, и хотя «убить» поток можно с помощью некоторых трюков C, вы рискуете ошибкой сегментации или утечкой памяти.
Вот более чистый способ сделать это.
class MyError(Exception):
pass
def Start(self):
try:
while self.is_active==True:
do it..
self.check_termination()
time.sleep(1)
self.check_termination()
do it..
self.check_termination()
time.sleep(1)
except MyError:
return
def check_termination(self):
if not self.is_active:
raise MyError
И вы можете вызвать self.check_termination() из любой функции, чтобы завершить этот цикл, не обязательно изнутри Start напрямую.
Обновлено: решение ShadowRanger лучше обрабатывает «прерываемое ожидание», я просто сохраняю его для реализации переключателя уничтожения для потока, который можно проверить из любого места внутри потока.
@ShadowRanger просто для сжатия кода до 1 строки вместо 2, что улучшает видимость кода, мне больше нравится ваш ответ, но я думаю, что его можно уменьшить с помощью такой вспомогательной функции, особенно когда эта функция вызывает больше функций внутри, и вам нужно распространять остановка петли.
Эх. Я на самом деле согласен с тем, что в этом случае он не сжимается; Я не хочу скрывать, что там код может выйти из цикла. Это также не очень заметно; вы скрыли то, что на самом деле происходит, в отдельной функции; имя функции дает подсказку о том, что она делает, но две строки будут немного более заметными, чем однострочный код, спрятанный в остальной части кода.
Во-первых, чтобы было ясно, жесткое уничтожение потока — ужасная идея для любого языка, и Python не поддерживает это; по крайней мере, риск того, что этот поток удерживает блокировку, которая никогда не будет разблокирована, что приведет к взаимоблокировке любого потока, который попытается получить ее, является фатальным недостатком.
Если вам вообще не нужен поток, вы можете создать его с аргументом daemon=True, и он умрет, если все потоки, не являющиеся демонами, в процессе завершились. Но если поток действительно должен умереть при правильной очистке (например, в нем могут быть операторы with или тому подобное, которые управляют очисткой ресурсов вне процесса, которые не будут очищены при завершении процесса), это не настоящее решение.
Тем не менее, вы можете избежать ожидания секунды или более, переключившись с использования простых bool и time.sleep на использование Event и использование метода .wait на нем. Это позволит немедленно прервать «сны» за небольшую плату, требующую от вас отменить свое состояние (поскольку Event.wait блокируется только тогда, когда оно ложно / не установлено, поэтому вам нужно, чтобы флаг основывался на том, когда вы должны остановиться, а не когда вы сейчас активны):
class Spam:
def __init__(self):
self.should_stop = threading.Event() # Create an unset event on init
def Start(self):
while not self.should_stop.is_set():
# do it..
if self.should_stop.wait(1):
break
# do it..
if self.should_stop.wait(1):
break
def Stop(self):
self.should_stop.set()
В современном Python (3.1 и выше) метод wait возвращает True, если событие было установлено (в начале ожидания или потому, что оно было установлено во время ожидания), и False в противном случае, поэтому всякий раз, когда wait возвращает True, это означает, что вам сказали остановиться и можно сразу break не в курсе. Вы также получаете уведомление почти сразу, вместо того, чтобы ждать до одной секунды, прежде чем вы сможете проверить флаг.
Это не приведет к немедленному выходу настоящего кода «сделай это..», но из того, что вы сказали, похоже, что эта часть кода не такая уж длинная, поэтому ожидание ее завершения не так уж и много. хлопот.
Если вы действительно хотите сохранить атрибут is_active для проверки того, активен ли он, вы можете определить его как свойство, которое меняет значение Event на противоположное, например:
@property
def is_active(self):
return not self.should_stop.is_set()
Насколько чище второй бит кода? Похоже, что замена return на break в вашем исходном коде дает все те же преимущества (возможность запуска дополнительного кода после завершения цикла перед возвратом), и я не вижу никакой реальной пользы от определения пользовательского исключения для выхода из одного петля слоя, когда простая break достигает тех же результатов с меньшим количеством шаблонов.