I am trying to write a simple PyQt widget that uses two workers. The first worker (Worker 1) starts its loop immediately, while the second one (Worker 2) sleeps for 2 seconds and then starts. I would like to get the following:
I have tried to get this by implementing the following code for the worker class:
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. 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).
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().
So, the only thread who is allowed to unlock is the thread of Worker1.
From your description of what you want to achieve, I think that lock() is the wrong approach. 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. If the worker is not blocking, it periodically tries to lock and unlock. If a blocking Worker is active, this leads to a blocking behaviour.
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_())
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.