在python中终止Timer线程的正确方法是什么?

时间:2019-08-13 03:01:53

标签: python-3.x multithreading gtk

关于终止Python计时器线程,我能找到的唯一另一个问题是how to terminate a timer thread in python,但这只能告诉我从阅读线程文档中已经知道的内容。

我正在用Python编写GTK应用程序。它通过d-bus与HexChat连接。因为当用户更改上下文(切换到不同的IRC通道或服务器窗口)时没有D总线信号,所以我的原始设计一直等到用户以某种方式与GUI交互以查看我们当前所处的上下文。这是需要切换的输入框,实际上不能用于切换上下文,因为如果用户开始键入,则会发出更改的信号,并且内容会保存到旧的上下文中,这是不正确的行为。

为解决此问题,我决定使用线程计时器任务每0.5秒检查一次当前上下文。线程函数检查当前上下文,更新类中的变量,然后以另一个0.5秒的延迟重新启动自身。效果很好,速度足够快,可以跟上用户经常切换频道的步伐。

但是,即使我在我的__del__()函数中添加TimerThread.cancel,也没有挂断程序,直到我给出键盘中断之前,它一直挂起。我还尝试再次执行TimerThread.cancel,sleep(0.1),TimerThread.cancel,以防万一我碰巧取消了上下文检查功能中的计时器。结果相同。我还尝试将TimerThread.cancel放在onDestroy函数中(该函数将依次调用__del__析构函数),但没有任何变化。 GTK循环退出,GUI消失,但是程序只是挂在控制台中,直到键盘中断。当我给键盘中断时,我从线程库中得到一个回溯错误。

还有其他终止线程的方法吗?在退出之前,我还需要采取其他措施来杀死线程库吗?我是否误解了Timer线程的工作方式?

以下是代码的相关部分: 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

1 个答案:

答案 0 :(得分:1)

我认为您遇到了赛车问题。尝试用锁保护contextUpdater变量。在这里,我添加了三个新函数,分别是从类start_updater调用的__init__stop_updater和从restart_updater方法调用的getContextTimer 。在退出应用程序之前,请调用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()