在 python 中终止计时器线程的正确方法是什么?
What is the proper way to terminate a Timer thread in python?
我能找到的关于终止 Python 计时器线程的唯一其他问题是这个问题: 但这只告诉我阅读线程文档时我已经知道的内容。
我正在 Python 中编写一个 GTK 应用程序。它通过 d-bus 与 HexChat 连接。因为当用户更改上下文(切换到不同的 IRC 频道或服务器 window)时没有 D-bus 信号,所以我的原始设计等到用户以某种方式与 GUI 交互以查看我们当前所处的上下文. 这样做的缺点是需要切换的输入框不能真正用于切换上下文,因为如果用户开始输入,则会发出更改的信号并将内容保存到旧上下文中,这是不正确的行为.
为了解决这个问题,我决定使用线程计时器任务每 0.5 秒检查一次当前上下文是什么。线程函数检查当前上下文,更新 class 中的变量,然后再延迟 0.5 秒重新启动。这非常有效,而且速度足够快,可以跟上用户经常切换频道的速度。
然而,即使我将 TimerThread.cancel 添加到我的 __del__()
函数,我的程序并没有退出,它只是挂起,直到我发出键盘中断。我还尝试再次执行 TimerThread.cancel, sleep(0.1), TimerThread.cancel 以防万一我碰巧取消了计时器,因为它在上下文检查功能中。同样的结果。我还尝试将 TimerThread.cancel 放在 onDestroy 函数中(这将依次调用 __del__
析构函数)但没有改变。 GTK 循环退出,GUI 消失,但程序只是挂在控制台中,直到键盘中断。当我 do 发出键盘中断时,我从线程库中得到一个回溯错误。
有没有其他方法可以终止线程?在退出之前,我还需要采取其他步骤来终止线程库吗?我是不是误解了定时器线程的工作原理?
以下是代码的相关部分:
https://paste.ubuntu.com/p/kQVfF78H5R/
编辑:
这是回溯,如果有帮助的话。
^CException ignored in: <module 'threading' from '/usr/lib/python3.7/threading.py'>
Traceback (most recent call last):
File "/usr/lib/python3.7/threading.py", line 1281, in _shutdown
t.join()
File "/usr/lib/python3.7/threading.py", line 1032, in join
self._wait_for_tstate_lock()
File "/usr/lib/python3.7/threading.py", line 1048, in _wait_for_tstate_lock
elif lock.acquire(block, timeout):
KeyboardInterrupt
我认为您 运行 处于竞争状态。尝试用锁保护 contextUpdater
变量。在这里,我添加了三个新函数,即从 class __init__
方法调用的 start_updater
,stop_updater
和从 [=] 调用的 restart_updater
17=] 方法。在退出您的应用程序之前,调用 stop_updater
方法将取消当前 运行 计时器(不要从 __del__
方法调用它):
def __init__(self):
self._lock = threading.Lock()
self.start_updater()
def start_updater(self):
with self._lock:
self._contextUpdater = threading.Timer(0.5, self.getContextTimer)
self._contextUpdater.start()
self._running = True
def stop_updater(self):
with self._lock:
if self._contextUpdater.is_alive():
self._contextUpdater.cancel()
self._running = False
def restart_updater(self):
with self._lock:
if not self._running:
return
self.start_updater()
def getContextTimer(self):
context = self.hcInterface.FindContext('', '')
try:
curChan = self.contextByID[int(context)]
except KeyError:
self.restart_updater()
return # we are in a context we don't care about
if curChan in self.inviteChannels:
self.getContext(None, 0)
elif curChan in self.supportChannels:
self.getContext(None, 1)
else:
self.getContext(None)
self.restart_updater()
我用以下玩具示例测试了这种方法:
class FooTimer(object):
def __init__(self):
self._lock = Lock()
self.start()
def start(self):
with self._lock:
self._timer = Timer(1, self.update)
self._timer.start()
self._running = True
def stop(self):
with self._lock:
if self._timer.is_alive():
self._timer.cancel()
self._running = False
def restart(self):
with self._lock:
if not self._running:
return
self.start()
def update(self):
print('tick')
self.restart()
我能找到的关于终止 Python 计时器线程的唯一其他问题是这个问题:
我正在 Python 中编写一个 GTK 应用程序。它通过 d-bus 与 HexChat 连接。因为当用户更改上下文(切换到不同的 IRC 频道或服务器 window)时没有 D-bus 信号,所以我的原始设计等到用户以某种方式与 GUI 交互以查看我们当前所处的上下文. 这样做的缺点是需要切换的输入框不能真正用于切换上下文,因为如果用户开始输入,则会发出更改的信号并将内容保存到旧上下文中,这是不正确的行为.
为了解决这个问题,我决定使用线程计时器任务每 0.5 秒检查一次当前上下文是什么。线程函数检查当前上下文,更新 class 中的变量,然后再延迟 0.5 秒重新启动。这非常有效,而且速度足够快,可以跟上用户经常切换频道的速度。
然而,即使我将 TimerThread.cancel 添加到我的 __del__()
函数,我的程序并没有退出,它只是挂起,直到我发出键盘中断。我还尝试再次执行 TimerThread.cancel, sleep(0.1), TimerThread.cancel 以防万一我碰巧取消了计时器,因为它在上下文检查功能中。同样的结果。我还尝试将 TimerThread.cancel 放在 onDestroy 函数中(这将依次调用 __del__
析构函数)但没有改变。 GTK 循环退出,GUI 消失,但程序只是挂在控制台中,直到键盘中断。当我 do 发出键盘中断时,我从线程库中得到一个回溯错误。
有没有其他方法可以终止线程?在退出之前,我还需要采取其他步骤来终止线程库吗?我是不是误解了定时器线程的工作原理?
以下是代码的相关部分: https://paste.ubuntu.com/p/kQVfF78H5R/
编辑: 这是回溯,如果有帮助的话。
^CException ignored in: <module 'threading' from '/usr/lib/python3.7/threading.py'>
Traceback (most recent call last):
File "/usr/lib/python3.7/threading.py", line 1281, in _shutdown
t.join()
File "/usr/lib/python3.7/threading.py", line 1032, in join
self._wait_for_tstate_lock()
File "/usr/lib/python3.7/threading.py", line 1048, in _wait_for_tstate_lock
elif lock.acquire(block, timeout):
KeyboardInterrupt
我认为您 运行 处于竞争状态。尝试用锁保护 contextUpdater
变量。在这里,我添加了三个新函数,即从 class __init__
方法调用的 start_updater
,stop_updater
和从 [=] 调用的 restart_updater
17=] 方法。在退出您的应用程序之前,调用 stop_updater
方法将取消当前 运行 计时器(不要从 __del__
方法调用它):
def __init__(self):
self._lock = threading.Lock()
self.start_updater()
def start_updater(self):
with self._lock:
self._contextUpdater = threading.Timer(0.5, self.getContextTimer)
self._contextUpdater.start()
self._running = True
def stop_updater(self):
with self._lock:
if self._contextUpdater.is_alive():
self._contextUpdater.cancel()
self._running = False
def restart_updater(self):
with self._lock:
if not self._running:
return
self.start_updater()
def getContextTimer(self):
context = self.hcInterface.FindContext('', '')
try:
curChan = self.contextByID[int(context)]
except KeyError:
self.restart_updater()
return # we are in a context we don't care about
if curChan in self.inviteChannels:
self.getContext(None, 0)
elif curChan in self.supportChannels:
self.getContext(None, 1)
else:
self.getContext(None)
self.restart_updater()
我用以下玩具示例测试了这种方法:
class FooTimer(object):
def __init__(self):
self._lock = Lock()
self.start()
def start(self):
with self._lock:
self._timer = Timer(1, self.update)
self._timer.start()
self._running = True
def stop(self):
with self._lock:
if self._timer.is_alive():
self._timer.cancel()
self._running = False
def restart(self):
with self._lock:
if not self._running:
return
self.start()
def update(self):
print('tick')
self.restart()