简体   繁体   English

如何在 PyQt 中使用另一个线程暂停线程中的循环

[英]How to pause loop in thread with another thread in PyQt

I am trying to write a simple PyQt widget that uses two workers.我正在尝试编写一个使用两个工人的简单 PyQt 小部件。 The first worker (Worker 1) starts its loop immediately, while the second one (Worker 2) sleeps for 2 seconds and then starts.第一个工作人员(工作人员 1)立即开始循环,而第二个工作人员(工作人员 2)休眠 2 秒然后开始。 I would like to get the following:我想得到以下内容:

  1. Worker 2 pauses Worker 1 execution Worker 2 暂停 Worker 1 的执行
  2. Worker 2 starts and completes the loop Worker 2 启动并完成循环
  3. Worker 2 finishes and lets Worker 1 complete its own loop. Worker 2 完成并让 Worker 1 完成自己的循环。

I have tried to get this by implementing the following code for the worker class:我试图通过为 worker 类实现以下代码来实现这一点:

class Worker(QObject):
    """
    Create worker class
    """
    worker_finished = pyqtSignal()
    ask_to_work = pyqtSignal()
    glob_lock = QMutex()

    def __init__(self, name, sleep_time = 0.5, loops = 10, sleep_at_start=False):
        super().__init__()
        self._name = name
        self.sleep_time = sleep_time
        self.loops = loops
        self._sleep_at_start = sleep_at_start
        self.worker_finished.connect(self.at_finish)
        self.ask_to_work.connect(self.pause_execution)

    @pyqtSlot()
    def run(self):
        #This is to make the worker sleep 2 seconds, then ask to run its loop
        if self._sleep_at_start:
            print("{:s} is sleeping.".format(self._name))
            sleep(2)
            print("{:s} has awaken.".format(self._name))
            self.ask_to_work.emit()

        #This acquires the lock and starts the worker loop
        self.glob_lock.lock()
        print("Lock acquired by {:s}".format(self._name))
        for ii in range(self.loops):
            print("I am {:s} and I am at step {:d}".format(self._name, ii))
            sleep(self.sleep_time)
        self.worker_finished.emit()

    #Function to attempt the interruption of the worker loop
    @pyqtSlot()
    def pause_execution(self):
        try:
            print("Request lock")
            self.glob_lock.unlock()
            print("Request succeded")
        except:
            print("No lock to release")

    @pyqtSlot()
    def at_finish(self):
        try:
            self.glob_lock.unlock()
            print("Successfully released lock at finish.")
        except:
            print("Releasing the lock at finish went wrong.")

While widget and the execution are:而小部件和执行是:

class MainApp(QWidget):
    """
    Make the widget
    """
    def __init__(self):
        super().__init__()
        #Set the widget layout
        layout = QHBoxLayout(self)

        self.button_run = QPushButton("Run workers")
        layout.addWidget(self.button_run)

        self._threads = []
        self._workers = []

        #Initialize the workers
        worker = Worker("Worker 1", loops=10)
        self._workers.append(worker)

        worker = Worker("Worker 2", sleep_time=1,
                        loops=1, sleep_at_start=True)
        self._workers.append(worker)

        #Connect workers' run methods to the widget button and put the workers into separate threads
        self.activate_and_connect_workers()

    def activate_and_connect_workers(self):
        for worker in self._workers:
            self.button_run.clicked.connect(worker.run)
            a_thread = QThread()
            self._threads.append(a_thread)
            worker.moveToThread(a_thread)
            a_thread.start()

if __name__ == '__main__':

    app = QApplication(sys.argv)
    window = MainApp()
    window.show()
    sys.exit(app.exec_())

What I would like to see in the terminal is:我想在终端看到的是:

Lock acquired by Worker 1
Worker 2 is sleeping
I am Worker 1 and I am at step 0
I am Worker 1 and I am at step 1
#...Some lines of Worker 1
Worker 2 has awaken.
Request lock
Request succeded
Lock acquired by Worker 2
I am Worker 2 and I am at step 0
#...Some lines of Worker 2
Worker 2 uccessfully released lock at finish.
Lock acquired by Worker 1
#...More lines of Worker 1
Worker 1 uccessfully released lock at finish.

This does not happen, because Worker 2 emits the signal to release the lock "to itself" and is not able to send it to Worker 1. This means the two processes are not synchronized.这不会发生,因为 Worker 2 发出了释放锁“给自己”的信号,并且无法将它发送给 Worker 1。这意味着两个进程不同步。 On top of that, the program crashes at the end of the Worker 1 loop (presumably because the lock is already released when Worker 1 restarts).最重要的是,程序在 Worker 1 循环结束时崩溃(大概是因为在 Worker 1 重新启动时锁已经释放了)。

What should I do to make the code work in the desired way?我应该怎么做才能使代码以所需的方式工作?

The documentation states that:文件指出:

When you call lock() in a thread, other threads that try to call lock() in the same place will block until the thread that got the lock calls unlock().当您在线程中调用 lock() 时,其他尝试在同一位置调用 lock() 的线程将阻塞,直到获得锁的线程调用 unlock()。

So, the only thread who is allowed to unlock is the thread of Worker1.因此,唯一允许解锁的线程是 Worker1 的线程。

From your description of what you want to achieve, I think that lock() is the wrong approach.从您对要实现的目标的描述来看,我认为 lock() 是错误的方法。 Locking "protects" a piece of code, so that only one thread can access it at a time.锁定“保护”了一段代码,以便一次只有一个线程可以访问它。

If you still want do use this a blocking mechanism, I would suggest the following pattern:如果您仍然想使用这种阻塞机制,我建议使用以下模式:

Add an "is_blocking" flag which determines whether the worker should get the lock along its lifetime.添加一个“is_blocking”标志,用于确定工作人员是否应该在其生命周期内获得锁。 If the worker is not blocking, it periodically tries to lock and unlock.如果 worker 没有阻塞,它会定期尝试锁定和解锁。 If a blocking Worker is active, this leads to a blocking behaviour.如果阻塞 Worker 处于活动状态,则会导致阻塞行为。

Here's a rough demo, based on your example:这是一个粗略的演示,基于您的示例:

import sys
from PyQt5.QtCore import QObject, pyqtSignal, QMutex, pyqtSlot, QThread, Qt
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QApplication
from time import sleep

glob_lock = QMutex()

class Worker(QObject):
    worker_finished = pyqtSignal()

    def __init__(self, name, sleep_time = 0.5, loops = 10, sleep_at_start=False, is_blocking=False):
        super().__init__()
        self._name = name
        self.sleep_time = sleep_time
        self.loops = loops
        self._sleep_at_start = sleep_at_start
        self.worker_finished.connect(self.at_finish)
        self.is_blocking = is_blocking

    @pyqtSlot()
    def run(self):
        if self._sleep_at_start:
            print("{:s} is sleeping.".format(self._name))
            sleep(2)
            print("{:s} has awaken.".format(self._name))
        if self.is_blocking:
            glob_lock.lock()
            print("Lock acquired by {:s}".format(self._name))
        for ii in range(self.loops):
            if not self.is_blocking:
                glob_lock.lock()
                glob_lock.unlock()
            print("I am {:s} and I am at step {:d}".format(self._name, ii))
            sleep(self.sleep_time)
        self.worker_finished.emit()


    @pyqtSlot()
    def at_finish(self):
        if self.is_blocking:
            glob_lock.unlock()
            print("Successfully released lock at finish.")
        else:
            print("Successfully finished.")

class MainApp(QWidget):
    def __init__(self):
        super().__init__()
        layout = QHBoxLayout(self)
        self.button_run = QPushButton("Run workers")
        layout.addWidget(self.button_run)

        self._threads = []
        self._workers = []

        worker = Worker("Worker 1", loops=10)
        self._workers.append(worker)
        worker = Worker("Worker 2", sleep_time=1, loops=5, sleep_at_start=True, is_blocking=True)
        self._workers.append(worker)
        self.activate_and_connect_workers()

    def activate_and_connect_workers(self):
        for worker in self._workers:
            self.button_run.clicked.connect(worker.run)
            a_thread = QThread()
            self._threads.append(a_thread)
            worker.moveToThread(a_thread)
            a_thread.start()

if __name__ == '__main__':

    app = QApplication(sys.argv)
    window = MainApp()
    window.show()
    sys.exit(app.exec_())

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM