python应用程序冻结在thread.join()上

时间:2018-01-30 18:32:15

标签: python multithreading python-3.x pyqt5 freeze

我在Python3和PyQt5中编写了一个简单的时间跟踪应用程序。时间在单独的线程中跟踪。该线程正在运行的功能无法访问GUI代码。在Windows10应用程序尝试关闭后冻结。这是由致电thread.join()引起的。我需要在任务管理器中结束该过程以关闭它。在Linux Mint上它运行正常。我使用线程库中的线程。它也不适用于QThread。如果我将thread.join()行注释掉它没有问题,但此线程运行的代码无法完成。

在Window类的__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

EDIT2: 删除这两行app关闭后没有任何问题。除了win32gui之外,还有其他方法可以在Windows上获取活动窗口名称吗?:

window = GetForegroundWindow()
active_window_name = GetWindowText(window)

2 个答案:

答案 0 :(得分:0)

我遇到了类似的问题,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_()

对我来说很好,只要qt_app启动,my_thread就会运行,并且完成退出后的工作。

编辑:拼写错误

答案 1 :(得分:0)

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

  

如果目标窗口由当前进程拥有,则GetWindowText会将WM_GETTEXT消息发送到指定的窗口或控件。如果目标窗口由另一个进程拥有并且具有标题,则GetWindowText将检索窗口标题文本。如果窗口没有标题,则返回值为空字符串。此行为是设计使然。如果拥有目标窗口的进程没有响应,它允许应用程序调用GetWindowText而不会无响应。 但是,如果目标窗口没有响应并且它属于调用应用程序,则GetWindowText将导致调用应用程序无响应

您正在尝试从Qt事件循环调用的函数(saveAndQuit)中加入线程。因此,在此函数返回之前,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()