python 申请冻结 thread.join()

python application freeze on thread.join()

我正在用 Python3 和 PyQt5 编写一个简单的时间跟踪应用程序。在单独的线程中跟踪时间。该线程是 运行 的函数不访问 GUI 代码。在 Windows10 上,应用程序在尝试关闭后冻结。是调用thread.join()引起的。我需要在任务管理器中结束进程来关闭它。在 Linux Mint 上运行良好。我正在使用线程库中的线程。它也不适用于 QThread。如果我注释掉 thread.join() 行,它会毫无问题地关闭,但是这个线程的 运行 代码没有完成。

线程在 Window class 的 __init__() 函数中初始化。

self.trackingThread = Thread(target = self.track)

负责跟踪时间的函数:

    def track(self):
    startTime = time()
    lastWindowChangeTime = startTime
    while self.running:
        # check if active window has changed
        if self.active_window_name != get_active_window_name():
            if self.active_window_name in self.applications_time:
                self.applications_time[self.active_window_name] += int(time() - lastWindowChangeTime) // 60 # time in minutes)
            else:
                self.applications_time[self.active_window_name] = int(time() - lastWindowChangeTime) // 60  # time in minutes

            lastWindowChangeTime = time()
            self.active_window_name = get_active_window_name()


    totalTime = int(time() - startTime) // 60  # time in minutes
    if date.today() in self.daily_time:
        self.daily_time[date.today()] += totalTime
    else:
        self.daily_time[date.today()] = totalTime

加入话题:

   def saveAndQuit(self):
        self.running = False
        self.trackingThread.join() # the line that's causing application freeze
        self.save()
        QApplication.instance().quit()

编辑: 例子: https://pastebin.com/vt3BfKJL

相关代码:

def get_active_window_name():
    active_window_name = ''

    if system() == 'Linux':
        active_window_name = check_output(['xdotool', 'getactivewindow', 'getwindowname']).decode('utf-8')
    elif system() == 'Windows':
        window = GetForegroundWindow()
        active_window_name = GetWindowText(window)

    return active_window_name

编辑2: 删除这两行后应用程序关闭没有任何问题。除了 win32gui 之外,还有其他方法可以在 Windows 上激活 window 名称吗?:

window = GetForegroundWindow()
active_window_name = GetWindowText(window)

我遇到了类似的问题,SO 上有人建议我使用这样的东西:

class MyThread(QThread):
    def __init__(self):
        super().__init__()
        # initialize your thread, use arguments in the constructor if needed

    def __del__(self):
        self.wait()

    def run(self):
        pass # Do whatever you need here

def run_qt_app():
    my_thread = MyThread()
    my_thread.start()

    qt_app = QApplication(sys.argv)
    qt_app.aboutToQuit.connect(my_thread.terminate)

    # Setup your window here

    return qt_app.exec_()

对我来说工作正常,my_thread 只要 qt_app 启动就运行,并在退出时完成它的工作。

编辑:拼写错误

出现此问题是因为 GetWindowText() 正在阻塞,因此您的线程永远无法加入。要了解原因,我们必须深入研究 win32 documentation

If the target window is owned by the current process, GetWindowText causes a WM_GETTEXT message to be sent to the specified window or control. If the target window is owned by another process and has a caption, GetWindowText retrieves the window caption text. If the window does not have a caption, the return value is a null string. This behavior is by design. It allows applications to call GetWindowText without becoming unresponsive if the process that owns the target window is not responding. However, if the target window is not responding and it belongs to the calling application, GetWindowText will cause the calling application to become unresponsive.

您正试图从 Qt 事件循环调用的函数 (saveAndQuit) 中加入线程。因此,在这个函数 returns 之前,Qt 事件循环不会处理任何消息。这意味着在另一个线程中对 GetWindowText 的调用已向 Qt 事件循环发送了一条消息,该消息在 saveAndQuit 完成之前不会被处理。但是,saveAndQuit 正在等待线程完成,因此出现死锁!

有几种解决死锁的方法,最容易实现的可能是从 Qt 事件循环递归调用 join,超时。它有点 "hacky",但其他替代方法意味着改变线程的行为方式或使用 QThreads。

因此,我将按如下方式修改您的 saveAndQuit

def saveAndQuit(self):
    self.running = False
    self.trackingThread.join(timeout=0.05) 
    # if thread is still alive, return control to the Qt event loop
    # and rerun this function in 50 milliseconds
    if self.trackingThread.is_alive():
        QTimer.singleShot(50, self.saveAndQuit)
        return
    # if the thread has ended, then save and quit!
    else:
        self.save()
        QApplication.instance().quit()