PyQt5的QThread在与GUI线程相同的线程上运行。

huangapple go评论67阅读模式
英文:

PyQt5 QThread is run on same thread as GUI thread

问题

I was attempting to load thumbnails for QPushButtons on the side, so I created a thread for every image to load (there are only a handful of them for now). I based this code on https://realpython.com/python-pyqt-qthread/.

class Worker(QObject):
    finished = pyqtSignal(QIcon)

    def run(self, path: str):
        import threading
        logging.debug(f'Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')
        icon = QIcon
        icon.addFile(path)
        logging.debug(f'Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later')
        self.finished.emit(icon)

# Step 2: Create a QThread object
self.thread = QThread()
# Step 3: Create a worker object
self.worker = Worker()
# Step 4: Move worker to the thread
self.worker.moveToThread(self.thread)
# Step 5: Connect signals and slots
self.thread.started.connect(lambda: self.worker.run(self.thumbnail_path))
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.set_icon)  # basically lambda icon: a_btn.setIcon(icon)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
# Step 6: Start the thread
logging.debug(f'Thread started at {(t1 := time.time())} seconds')
self.thread.start(priority=0)

I was expecting to see parallel execution (4 core / 8 thread CPU), but according to the logs, the thread IDs are identical:

2023-05-14T13:27:04 Thread started at 1684063624.1749578 seconds
2023-05-14T13:27:04 Thread started at 1684063624.3233523 seconds
2023-05-14T13:27:04 Thread started at 1684063624.3353844 seconds
2023-05-14T13:27:04 Thread started at 1684063624.5469468 seconds
2023-05-14T13:27:04 Thread started at 1684063624.7655282 seconds
2023-05-14T13:27:06 Widget setup completed at 1684063626.6284823 seconds
2023-05-14T13:27:06 Worker started at 1684063626.6485348 seconds on thread 10100
2023-05-14T13:27:06 Worker finished @ 1684063626.6585634 seconds, 0.01 seconds later
2023-05-14T13:27:06 Worker started at 1684063626.6585634 seconds on thread 10100
2023-05-14T13:27:06 Worker finished @ 1684063626.8139749 seconds, 0.155 seconds later
2023-05-14T13:27:06 Worker started at 1684063626.814978 seconds on thread 10100
2023-05-14T13:27:06 Worker finished @ 1684063626.8250043 seconds, 0.01 seconds later
2023-05-14T13:27:06 Worker started at 1684063626.826007 seconds on thread 10100
2023-05-14T13:27:07 Worker finished @ 1684063627.0385983 seconds, 0.213 seconds later
2023-05-14T13:27:07 Worker started at 1684063627.0395803 seconds on thread 10100
2023-05-14T13:27:07 Worker finished @ 1684063627.2581596 seconds, 0.219 seconds later

The files to be loaded range from 300kb to 50MB, and are stored on a 7200RPM HDD.

What might cause this behavior?

英文:

I was attemtping to load thumbnails for QPushButtons on the side, so I created a thread for every image to load (there are only a handful of them for now). I based this code on https://realpython.com/python-pyqt-qthread/.

class Worker(QObject):
    finished = pyqtSignal(QIcon)

    def run(self, path: str):
        import threading
        logging.debug(f'Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')
        icon = QIcon
        icon.addFile(path)
        logging.debug(f'Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later')
        self.finished.emit(icon)

# Step 2: Create a QThread object
self.thread = QThread()
# Step 3: Create a worker object
self.worker = Worker()
# Step 4: Move worker to the thread
self.worker.moveToThread(self.thread)
# Step 5: Connect signals and slots
self.thread.started.connect(lambda: self.worker.run(self.thumbnail_path))
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.set_icon)  # basically lambda icon: a_btn.setIcon(icon)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
# Step 6: Start the thread
logging.debug(f'Thread started at {(t1 := time.time())} seconds')
self.thread.start(priority=0)

I was expecting to see parallel execution (4 core / 8 thread CPU), but according to the logs, the thread IDs are identical:

2023-05-14T13:27:04 Thread started at 1684063624.1749578 seconds
2023-05-14T13:27:04 Thread started at 1684063624.3233523 seconds
2023-05-14T13:27:04 Thread started at 1684063624.3353844 seconds
2023-05-14T13:27:04 Thread started at 1684063624.5469468 seconds
2023-05-14T13:27:04 Thread started at 1684063624.7655282 seconds
2023-05-14T13:27:06 Widget setup completed at 1684063626.6284823 seconds
2023-05-14T13:27:06 Worker started at 1684063626.6485348 seconds on thread 10100
2023-05-14T13:27:06 Worker finished @ 1684063626.6585634 seconds, 0.01 seconds later
2023-05-14T13:27:06 Worker started at 1684063626.6585634 seconds on thread 10100
2023-05-14T13:27:06 Worker finished @ 1684063626.8139749 seconds, 0.155 seconds later
2023-05-14T13:27:06 Worker started at 1684063626.814978 seconds on thread 10100
2023-05-14T13:27:06 Worker finished @ 1684063626.8250043 seconds, 0.01 seconds later
2023-05-14T13:27:06 Worker started at 1684063626.826007 seconds on thread 10100
2023-05-14T13:27:07 Worker finished @ 1684063627.0385983 seconds, 0.213 seconds later
2023-05-14T13:27:07 Worker started at 1684063627.0395803 seconds on thread 10100
2023-05-14T13:27:07 Worker finished @ 1684063627.2581596 seconds, 0.219 seconds later

The files to be loaded range from 300kb to 50MB, and are stored on a 7200RPM HDD.

What might cause this behaviour?

I am using PyCharm 2022 Professional, Python 3.11, Windows 10.

答案1

得分: 1

这是您要翻译的内容:

"这是因为您在调用self.thread.started.connect(lambda: ...)时没有为Qt提供一个正确的线程关联的提示。您应该使用Worker的一个方法(或者一个槽函数)。但是Worker.run()带有一个参数(path),您不能将其连接到QThread.started信号。

如果您不介意使用实例属性,可以这样做。

...
class Worker(QObject):
    ...
    def run(self):
        ...
        icon = QIcon()
        icon.addFile(self.path)
        ...
...
self.worker.path = self.thumbnail_path
self.thread.started.connect(self.worker.run)
...

如果您想保留方法签名,可以这样做。

...
class Worker(QObject):
    def post(self, func):
        # 这可以在主线程中调用。
        e = QEvent(QEvent.User)
        e.func = func
        # 这是线程安全的。
        QApplication.postEvent(self, e)
    def customEvent(self, e):
        # 这将在工作线程中调用。
        e.func()
    ...
    def run(self, path: str):
        ...
...
self.thread.started.connect(lambda: self.worker.post(
    lambda: self.worker.run(self.thumbnail_path)))

顺便提一下,在大多数平台上,您可以安全地使用信号在线程之间传递QIcon对象,这与问题评论中其他人说的不同。(如果您感兴趣,可以查看详情。)"

英文:

It's because you didn't give Qt a hint for determining a correct thread affinity when you called self.thread.started.connect(lambda: ...). You should use a method(or a slot) of the Worker. But the Worker.run() has an argument(path) and you cannot connect it to the QThread.started signal.

If you don't mind using an instance attribute, do like this.

...
class Worker(QObject):
    ...
    def run(self):
        ...
        icon = QIcon()
        icon.addFile(self.path)
        ...
...
self.worker.path = self.thumbnail_path
self.thread.started.connect(self.worker.run)
...

If you want to keep the method signature, do like this.

...
class Worker(QObject):
    def post(self, func):
        # This can be called in the main thread.
        e = QEvent(QEvent.User)
        e.func = func
        # This is thread-safe.
        QApplication.postEvent(self, e)
    def customEvent(self, e):
        # This will be called in the worker thread.
        e.func()
    ...
    def run(self, path: str):
        ...
...
self.thread.started.connect(lambda: self.worker.post(
    lambda: self.worker.run(self.thumbnail_path)))

As a side note, you can safely pass QIcon objects between threads using signals in most platforms, unlike what others said in the comments on the question. (See the detail if you are interested.)

答案2

得分: 1

I have translated the provided code. Here is the translated code:

好的在修订我关于PyQt和QThreads的非常有限的知识我得到了3个答案其中包含一个最小可重现示例

第一个基于第一个答案与本帖相同@relent95提供[链接][1] 代码如下

```python
import sys
import logging
import threading
import time
from PyQt5.QtCore import QObject, QThread, pyqtSignal, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

logger = logging.getLogger("test")
logger.setLevel(level=logging.DEBUG)

logFileFormatter = logging.Formatter(
    fmt="%(levelname)s %(asctime)s (%(relativeCreated)d) \t %(pathname)s F%(funcName)s L%(lineno)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
logger.addHandler(console)

class Worker(QObject):
    finished = pyqtSignal(QIcon)

    def run(self):
        import threading
        logger.debug(
            f'Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')
        icon = QIcon()
        icon.addFile(self.path)
        logger.debug(
            f'Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later')
        self.finished.emit(icon)

class MyClass(QObject):

    def __init__(self, parent=None):
        super(MyClass, self).__init__(parent)

        self.thumbnail_path = 'test.png'

        self.thread = QThread()
        self.worker = Worker()

        self.worker.path = self.thumbnail_path

        self.worker.moveToThread(self.thread)

        logger.debug(
            f'self.thread.isRunning() : {self.thread.isRunning()}, on thread {threading.get_ident()}')

        self.thread.started.connect(self.worker.run)

        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.set_icon)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)

        logger.debug(
            f'Thread started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')

        self.thread.start(priority=0)

        logger.debug(
            f'self.thread.isRunning() : {self.thread.isRunning()}, on thread {threading.get_ident()}')

    def set_icon(self):

        logger.debug('set_icon')

        app.quit()

if __name__ == "__main__":

    app = QApplication(sys.argv)
    a = MyClass()
    sys.exit(app.exec())

输出:

self.thread.isRunning() : False, on thread 139800282842944
Thread started at 1684857067.3460085 seconds on thread 139800282842944
self.thread.isRunning() : True, on thread 139800282842944
Worker started at 1684857067.3478193 seconds on thread 139798901659392
Worker finished @ 1684857067.3550224 seconds, 0.00721 seconds later
set_icon

第二个基于[链接][2],代码如下:

import sys
import logging
import threading
import time
from PyQt5.QtCore import QObject, QThread, pyqtSignal, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

logger = logging.getLogger("test")
logger.setLevel(level=logging.DEBUG)

logFileFormatter = logging.Formatter(
    fmt="%(levelname)s %(asctime)s (%(relativeCreated)d) \t %(pathname)s F%(funcName)s L%(lineno)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
logger.addHandler(console)

class Worker(QObject):
    finished = pyqtSignal(QIcon)

    def __init__(self, path=None):
        super(Worker, self).__init__()

        self.path = path

    def run(self):
        import threading
        logger.debug(
            f'Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')
        icon = QIcon()
        icon.addFile(self.path)
        logger.debug(
            f'Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later')
        self.finished.emit(icon)

class MyClass(QObject):

    def __init__(self, parent=None):
        super(MyClass, self).__init__(parent)

        self.thumbnail_path = 'test.png'

        self.thread = QThread()
        self.worker = Worker(self.thumbnail_path)

        self.worker.moveToThread(self.thread)

        logger.debug(
            f'self.thread.isRunning() : {self.thread.isRunning()}, on thread {threading.get_ident()}')

        self.thread.started.connect(self.worker.run)

        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.set_icon)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)

        logger.debug(
            f'Thread started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')

        self.thread.start(priority=0)

        logger.debug(
            f'self.thread.isRunning() : {self.thread.isRunning()}, on thread {threading.get_ident()}')

    def set_icon(self):

        logger.debug('set_icon')

        app.quit()

if __name__ == "__main__":

    app = QApplication(sys.argv)
    a = MyClass()
    sys.exit(app.exec())

输出:

self.thread.isRunning() : False, on thread 139884701431616
Thread started at 1684857105.0672066 seconds on thread 139884701431616
self.thread.isRunning() : True, on thread 139884701431616
Worker started at 1684857105.0705557 seconds on thread 139883385448192
Worker finished @ 1684857105.0839903 seconds, 0.0134 seconds later
set_icon

第三个我添加了Signals和Slots,代码如下:

import sys
import logging
import threading
import time
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

logger = logging.getLogger("test")
logger.setLevel(level=logging.DEBUG)

logFileFormatter = logging.Formatter(
    fmt="%(levelname)s %(asctime)s (%(relativeCreated)d) \t %(pathname)s F%(funcName)s L%(lineno)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
logger.addHandler(console)

class Worker(QObject):
    finished = pyqtSignal(QIcon)

    @pyqt

<details>
<summary>英文:</summary>

OK, while revising all my (very poor knowledge ) on PyQt and QThreads I ended up with 3 answers, carrying a minimal reproducible example.

First one is based on first answer, in this same post, by @relent95, [https://stackoverflow.com/a/76296571/9877065][1] , code :

import sys

import logging

import threading

from time import sleep

import time

from PyQt5.QtCore import QObject, QThread, pyqtSignal, Qt

from PyQt5.QtGui import QIcon

from PyQt5.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)

logger = logging.getLogger("test")
logger.setLevel(level=logging.DEBUG)

logFileFormatter = logging.Formatter(
fmt=f"%(levelname)s %(asctime)s (%(relativeCreated)d) \t %(pathname)s F%(funcName)s L%(lineno)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
logger.addHandler(console)

class Worker(QObject):
finished = pyqtSignal(QIcon)

def run(self):
import threading
logger.debug(
f&#39;Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
icon = QIcon()
icon.addFile(self.path)
logger.debug(
f&#39;Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later&#39;)
self.finished.emit(icon)

class MyClass(QObject):

# All QObjects receive a parent argument (default to None)
def __init__(self, parent=None):
super(MyClass, self).__init__(parent)   # Call parent initializer.
self.thumbnail_path = &#39;test.png&#39;
# Step 2: Create a QThread object
self.thread = QThread()
# Step 3: Create a worker object
self.worker = Worker()
self.worker.path = self.thumbnail_path
# Step 4: Move worker to the thread
self.worker.moveToThread(self.thread)
logger.debug(
f&#39;self.thread.isRunning() : {self.thread.isRunning()}, on thread {threading.get_ident()}&#39;)
# Step 5: Connect signals and slots
# self.thread.started.connect(lambda: self.worker.run(self.thumbnail_path))
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
# basically lambda icon: a_btn.setIcon(icon)
self.worker.finished.connect(self.set_icon)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
# Step 6: Start the thread
logger.debug(
f&#39;Thread started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
self.thread.start(priority=0)
logger.debug(
f&#39;self.thread.isRunning() : {self.thread.isRunning()}, on thread {threading.get_ident()}&#39;)
def set_icon(self):
# print(&#39;set_icon&#39;)
logger.debug(&#39;set_icon&#39;)
app.quit()
pass

if name == "main":

app = QApplication(sys.argv)
a = MyClass()
sys.exit(app.exec())

output:

self.thread.isRunning() : False, on thread 139800282842944
Thread started at 1684857067.3460085 seconds on thread 139800282842944
self.thread.isRunning() : True, on thread 139800282842944
Worker started at 1684857067.3478193 seconds on thread 139798901659392
Worker finished @ 1684857067.3550224 seconds, 0.00721 seconds later
set_icon


Second one is based on [https://stackoverflow.com/questions/74348042/how-to-assign-variables-to-worker-thread-in-python-pyqt5][2] , code: 

import sys

import logging

import threading

from time import sleep

import time

from PyQt5.QtCore import QObject, QThread, pyqtSignal, Qt

from PyQt5.QtGui import QIcon

from PyQt5.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)

logger = logging.getLogger("test")
logger.setLevel(level=logging.DEBUG)

logFileFormatter = logging.Formatter(
fmt=f"%(levelname)s %(asctime)s (%(relativeCreated)d) \t %(pathname)s F%(funcName)s L%(lineno)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
logger.addHandler(console)

class Worker(QObject):
finished = pyqtSignal(QIcon)

def __init__(self, path=None):
super(Worker, self).__init__()
self.path = path
def run(self):
import threading
logger.debug(
f&#39;Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
icon = QIcon()
icon.addFile(self.path)
logger.debug(
f&#39;Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later&#39;)
self.finished.emit(icon)

class MyClass(QObject):

# All QObjects receive a parent argument (default to None)
def __init__(self, parent=None):
super(MyClass, self).__init__(parent)   # Call parent initializer.
self.thumbnail_path = &#39;test.png&#39;
# Step 2: Create a QThread object
self.thread = QThread()
# Step 3: Create a worker object
self.worker = Worker(self.thumbnail_path)
# Step 4: Move worker to the thread
self.worker.moveToThread(self.thread)
logger.debug(
f&#39;self.thread.isRunning() : {self.thread.isRunning()}, on thread {threading.get_ident()}&#39;)
# Step 5: Connect signals and slots
# self.thread.started.connect(lambda: self.worker.run(self.thumbnail_path))
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
# basically lambda icon: a_btn.setIcon(icon)
self.worker.finished.connect(self.set_icon)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
# Step 6: Start the thread
logger.debug(
f&#39;Thread started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
self.thread.start(priority=0)
logger.debug(
f&#39;self.thread.isRunning() : {self.thread.isRunning()}, on thread {threading.get_ident()}&#39;)
def set_icon(self):
# print(&#39;set_icon&#39;)
logger.debug(&#39;set_icon&#39;)
app.quit()
pass

if name == "main":

app = QApplication(sys.argv)
a = MyClass()
sys.exit(app.exec())

output:

self.thread.isRunning() : False, on thread 139884701431616
Thread started at 1684857105.0672066 seconds on thread 139884701431616
self.thread.isRunning() : True, on thread 139884701431616
Worker started at 1684857105.0705557 seconds on thread 139883385448192
Worker finished @ 1684857105.0839903 seconds, 0.0134 seconds later
set_icon


Third one I added Signals and Slots, code :

import sys

import logging

import threading

from time import sleep

import time

from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot, Qt

from PyQt5.QtGui import QIcon

from PyQt5.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)

logger = logging.getLogger("test")
logger.setLevel(level=logging.DEBUG)

logFileFormatter = logging.Formatter(
fmt=f"%(levelname)s %(asctime)s (%(relativeCreated)d) \t %(pathname)s F%(funcName)s L%(lineno)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
logger.addHandler(console)

class Worker(QObject):
finished = pyqtSignal(QIcon)

@pyqtSlot(str)
def run(self, n):
print(&#39;n : &#39;, n, type(n))
import threading
logger.debug(
f&#39;Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
icon = QIcon()
# icon.addFile(n)
logger.debug(
f&#39;Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later&#39;)
self.finished.emit(icon)

class MyClass(QMainWindow):

work_requested = pyqtSignal(str)
def __init__(self, *args, **kwargs):
super(MyClass, self).__init__(*args, **kwargs)
# All QObjects receive a parent argument (default to None)
# def __init__(self, parent=None):
#     super(MyClass, self).__init__(parent)   # Call parent initializer.
self.thumbnail_path = &#39;test.png&#39;
# Step 2: Create a QThread object
self.thread = QThread()
# Step 3: Create a worker object
self.worker = Worker()
# Step 4: Move worker to the thread
self.worker.moveToThread(self.thread)
logger.debug(
f&#39;self.thread.isRunning() : {self.thread.isRunning()}, on thread {threading.get_ident()}&#39;)
# Step 5: Connect signals and slots
# self.thread.started.connect(lambda: self.worker.run(self.thumbnail_path))
self.work_requested.connect(self.worker.run)
self.work_requested.emit(&#39;test.png&#39;)
self.worker.finished.connect(self.thread.quit)
# basically lambda icon: a_btn.setIcon(icon)
self.worker.finished.connect(self.set_icon)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
# Step 6: Start the thread
logger.debug(
f&#39;Thread started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
self.thread.start(priority=0)
logger.debug(
f&#39;self.thread.isRunning() : {self.thread.isRunning()}, on thread {threading.get_ident()}&#39;)
def set_icon(self):
# print(&#39;set_icon&#39;)
logger.debug(&#39;set_icon&#39;)
app.quit()
pass

if name == "main":

app = QApplication(sys.argv)
a = MyClass()
sys.exit(app.exec())

output:

self.thread.isRunning() : False, on thread 140092591085376
Thread started at 1684857172.9183104 seconds on thread 140092591085376
self.thread.isRunning() : True, on thread 140092591085376
Worker started at 1684857172.9207199 seconds on thread 140091345303296
Worker finished @ 1684857172.9222894 seconds, 0.00158 seconds later
set_icon
n : test.png <class 'str'>


**Important note**
Used this approach to have a non GUI PyQt app : [https://stackoverflow.com/questions/25392471/python-pyqt-is-it-possible-to-use-qthread-with-a-non-gui-program][3] , probably would have be better to use this answer : [https://stackoverflow.com/questions/30611290/how-use-pyqt-without-gui][4]
----------------------------------------------------------------
The [minimal-reproducible-example][5] I started from is :

import sys

import logging

import threading

from time import sleep

import time

from PyQt5.QtCore import QObject, QThread, pyqtSignal, Qt

from PyQt5.QtGui import QIcon

from PyQt5.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)

logger = logging.getLogger("test")
logger.setLevel(level=logging.DEBUG)

logFileFormatter = logging.Formatter(
fmt=f"%(levelname)s %(asctime)s (%(relativeCreated)d) \t %(pathname)s F%(funcName)s L%(lineno)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
logger.addHandler(console)

class Worker(QObject):
finished = pyqtSignal(QIcon)

def run(self, path: str):
import threading
logger.debug(
f&#39;Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
icon = QIcon()
icon.addFile(path)
logger.debug(
f&#39;Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later&#39;)
self.finished.emit(icon)

class MyClass(QObject):

# All QObjects receive a parent argument (default to None)
def __init__(self, parent=None):
super(MyClass, self).__init__(parent)   # Call parent initializer.
self.thumbnail_path = &#39;test.png&#39;
# Step 2: Create a QThread object
self.thread = QThread()
# Step 3: Create a worker object
self.worker = Worker()
# Step 4: Move worker to the thread
self.worker.moveToThread(self.thread)
logger.debug(
f&#39;self.thread.isRunning() : {self.thread.isRunning()}, on thread {threading.get_ident()}&#39;)
# Step 5: Connect signals and slots
# self.thread.started.connect(lambda: self.worker.run(self.thumbnail_path))
self.thread.started.connect(lambda: self.worker.run(self.thumbnail_path))
self.worker.finished.connect(self.thread.quit)
# basically lambda icon: a_btn.setIcon(icon)
self.worker.finished.connect(self.set_icon)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
# Step 6: Start the thread
logger.debug(
f&#39;Thread started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
self.thread.start(priority=0)
logger.debug(
f&#39;self.thread.isRunning() : {self.thread.isRunning()}, on thread {threading.get_ident()}&#39;)
def set_icon(self):
logger.debug(&#39;set_icon&#39;)
app.quit()
pass

if name == "main":

app = QApplication(sys.argv)
a = MyClass()
sys.exit(app.exec())

output:

self.thread.isRunning() : False, on thread 140687765116736
Thread started at 1684858090.5508475 seconds on thread 140687765116736
self.thread.isRunning() : True, on thread 140687765116736
Worker started at 1684858090.5602846 seconds on thread 140687765116736
Worker finished @ 1684858090.5662382 seconds, 0.00596 seconds later
set_icon


[1]: https://stackoverflow.com/a/76296571/9877065
[2]: https://stackoverflow.com/questions/74348042/how-to-assign-variables-to-worker-thread-in-python-pyqt5
[3]: https://stackoverflow.com/questions/25392471/python-pyqt-is-it-possible-to-use-qthread-with-a-non-gui-program
[4]: https://stackoverflow.com/questions/30611290/how-use-pyqt-without-gui
[5]: https://stackoverflow.com/help/minimal-reproducible-example
</details>
# 答案3
**得分**: 0
这是您提供的Python代码的翻译:
```python
这不是一个答案,而是尝试展示一个类似于OP问题的MRE示例,大致上可以工作。我想下一步应该是只有一个Long-Running Task按钮,每次按下时都会启动一个新线程(取自[https://realpython.com/python-pyqt-qthread/][1]):
```python
import logging
import threading
import sys
from time import sleep
import time
from PyQt5.QtCore import QObject, QThread, pyqtSignal, Qt
from PyQt5.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
# 代码截取...
logger = logging.getLogger("test")
logger.setLevel(level=logging.DEBUG)
logFileFormatter = logging.Formatter(
fmt="%(levelname)s %(asctime)s (%(relativeCreated)d) \t F%(funcName)s L%(lineno)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
fileHandler = logging.FileHandler(filename='test.log', mode='w+')
fileHandler.setFormatter(logFileFormatter)
fileHandler.setLevel(level=logging.DEBUG)
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
logger.addHandler(fileHandler)
logger.addHandler(console)
# 步骤1:创建一个工作类
class Worker(QObject):
finished = pyqtSignal()
progress = pyqtSignal(int)
def run(self):
"""长时间运行的任务。"""
# 导入线程
logger.debug(f'Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')
for i in range(5):
sleep(1)
self.progress.emit(i + 1)
logger.debug(f'Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later on thread {threading.get_ident()}')
self.finished.emit()
class Window(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.clicksCount = 0
self.setupUi()
def setupUi(self):
self.setWindowTitle("Freezing GUI")
self.resize(300, 250)
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
# 创建并连接小部件
self.clicksLabel = QLabel("Counting: 0 clicks", self)
self.clicksLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.stepLabel = QLabel("Long-Running Step: 0")
self.stepLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.stepLabel2 = QLabel("Long-Running Step: 0")
self.stepLabel2.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.countBtn = QPushButton("Click me!", self)
self.countBtn.clicked.connect(self.countClicks)
self.longRunningBtn = QPushButton("Long-Running Task!", self)
self.longRunningBtn.clicked.connect(self.runLongTask)
self.longRunningBtn2 = QPushButton("Long-Running Task!", self)
self.longRunningBtn2.clicked.connect(self.runLongTask2)
# 设置布局
layout = QVBoxLayout()
layout.addWidget(self.clicksLabel)
layout.addWidget(self.countBtn)
layout.addStretch()
layout.addWidget(self.stepLabel)
layout.addWidget(self.longRunningBtn)
layout.addWidget(self.stepLabel2)
layout.addWidget(self.longRunningBtn2)
self.centralWidget.setLayout(layout)
print('main thread on : ', threading.get_ident())
logger.debug(f'Main Thread started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')
def countClicks(self):
self.clicksCount += 1
self.clicksLabel.setText(f"Counting: {self.clicksCount} clicks")
def reportProgress(self, n):
self.stepLabel.setText(f"Long-Running Step: {n}")
def reportProgress2(self, n):
self.stepLabel2.setText(f"Long-Running Step: {n}")
def runLongTask(self):
# 步骤2:创建一个QThread对象
self.thread = QThread()
# 步骤3:创建一个工作者对象
self.worker = Worker()
# 步骤4:将工作者移动到线程中
self.worker.moveToThread(self.thread)
# 步骤5:连接信号和槽
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.worker.progress.connect(self.reportProgress)
# 步骤6:启动线程
logger.debug(f'Thread started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')
self.thread.start(priority=0)
# 最终重置
self.longRunningBtn.setEnabled(False)
self.thread.finished.connect(
lambda: self.longRunningBtn.setEnabled(True)
)
self.thread.finished.connect(
lambda: self.stepLabel.setText("Long-Running Step: 0")
)
def runLongTask2(self):
# 步骤2:创建一个QThread对象
self.thread2 = QThread()
# 步骤3:创建一个工作者对象
self.worker2 = Worker()
# 步骤4:将工作者移动到线程中
self.worker2.moveToThread(self.thread2)
# 步骤5:连接信号和槽
self.thread2.started.connect(self.worker2.run)
self.worker2.finished.connect(self.thread2.quit)
self.worker2.finished.connect(self.worker2.deleteLater)
self.thread2.finished.connect(self.thread2.deleteLater)
self.worker2.progress.connect(self.reportProgress2)
# 步骤6:启动线程
logger.debug(f'Thread2 started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')
self.thread2.start(priority=0)
# 最终重置
self.longRunningBtn2.setEnabled(False)
self.thread2.finished.connect(
lambda: self.longRunningBtn2.setEnabled(True)
)
self.thread2.finished.connect(
lambda: self.stepLabel2.setText("Long-Running2 Step: 0")
)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())

使用QThreadPool,如[https://www.pythonguis.com/tutorials/multithreading-pyqt-applications-qthreadpool/][2]所示:

import logging
import threading
import sys
from time import sleep
import time
from PyQt5.QtCore import QObject, QThreadPool, pyqtSignal, pyqtSlot, Qt, QRunnable
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow

<details>
<summary>英文:</summary>

this is not an answer but trying to show an example that is an MRE of something that works more or less like the OP question. I guess next step would be to have just one Long-Running Task button that would start  new thread every time is pushed ( taken from [https://realpython.com/python-pyqt-qthread/][1] ):

import logging

import threading

import sys

from time import sleep

import time

from PyQt5.QtCore import QObject, QThread, pyqtSignal, Qt

from PyQt5.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)

Snip...

logger = logging.getLogger("test")
logger.setLevel(level=logging.DEBUG)

logFileFormatter = logging.Formatter(
fmt=f"%(levelname)s %(asctime)s (%(relativeCreated)d) \t F%(funcName)s L%(lineno)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
fileHandler = logging.FileHandler(filename='test.log' , mode = 'w+')
fileHandler.setFormatter(logFileFormatter)
fileHandler.setLevel(level=logging.DEBUG)

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)

logger.addHandler(fileHandler)
logger.addHandler(console)

Step 1: Create a worker class

class Worker(QObject):
finished = pyqtSignal()
progress = pyqtSignal(int)

def run(self):
&quot;&quot;&quot;Long-running task.&quot;&quot;&quot;
# import threading
logger.debug(f&#39;Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
for i in range(5):
sleep(1)
self.progress.emit(i + 1)
logger.debug(f&#39;Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later on thread {threading.get_ident()}&#39;)
self.finished.emit()

class Window(QMainWindow):

def __init__(self, parent=None):
super().__init__(parent)
self.clicksCount = 0
self.setupUi()
def setupUi(self):
self.setWindowTitle(&quot;Freezing GUI&quot;)
self.resize(300, 250)
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
# Create and connect widgets
self.clicksLabel = QLabel(&quot;Counting: 0 clicks&quot;, self)
self.clicksLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.stepLabel = QLabel(&quot;Long-Running Step: 0&quot;)
self.stepLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.stepLabel2 = QLabel(&quot;Long-Running Step: 0&quot;)
self.stepLabel2.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.countBtn = QPushButton(&quot;Click me!&quot;, self)
self.countBtn.clicked.connect(self.countClicks)
self.longRunningBtn = QPushButton(&quot;Long-Running Task!&quot;, self)
self.longRunningBtn.clicked.connect(self.runLongTask)
self.longRunningBtn2 = QPushButton(&quot;Long-Running Task!&quot;, self)
self.longRunningBtn2.clicked.connect(self.runLongTask2)
# Set the layout
layout = QVBoxLayout()
layout.addWidget(self.clicksLabel)
layout.addWidget(self.countBtn)
layout.addStretch()
layout.addWidget(self.stepLabel)
layout.addWidget(self.longRunningBtn)
layout.addWidget(self.stepLabel2)
layout.addWidget(self.longRunningBtn2)
self.centralWidget.setLayout(layout)   
print(&#39;main thread on : &#39;, threading.get_ident())
logger.debug(f&#39;Main Thread started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
def countClicks(self):
self.clicksCount += 1
self.clicksLabel.setText(f&quot;Counting: {self.clicksCount} clicks&quot;)
def reportProgress(self, n):
self.stepLabel.setText(f&quot;Long-Running Step: {n}&quot;)
def reportProgress2(self, n):
self.stepLabel2.setText(f&quot;Long-Running Step: {n}&quot;)
def runLongTask(self):
# Step 2: Create a QThread object
self.thread = QThread()
# Step 3: Create a worker object
self.worker = Worker()
# Step 4: Move worker to the thread
self.worker.moveToThread(self.thread)
# Step 5: Connect signals and slots
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.worker.progress.connect(self.reportProgress)
# Step 6: Start the thread
# self.thread.start()
logger.debug(f&#39;Thread started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
self.thread.start(priority=0)
# Final resets
self.longRunningBtn.setEnabled(False)
self.thread.finished.connect(
lambda: self.longRunningBtn.setEnabled(True)
)
self.thread.finished.connect(
lambda: self.stepLabel.setText(&quot;Long-Running Step: 0&quot;)
)
def runLongTask2(self):
# Step 2: Create a QThread object
self.thread2 = QThread()
# Step 3: Create a worker object
self.worker2 = Worker()
# Step 4: Move worker to the thread
self.worker2.moveToThread(self.thread2)
# Step 5: Connect signals and slots
self.thread2.started.connect(self.worker2.run)
self.worker2.finished.connect(self.thread2.quit)
self.worker2.finished.connect(self.worker2.deleteLater)
self.thread2.finished.connect(self.thread2.deleteLater)
self.worker2.progress.connect(self.reportProgress2)
# Step 6: Start the thread
# self.thread.start()
logger.debug(f&#39;Thread2 started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
self.thread2.start(priority=0)
# Final resets
self.longRunningBtn2.setEnabled(False)
self.thread2.finished.connect(
lambda: self.longRunningBtn2.setEnabled(True)
)
self.thread2.finished.connect(
lambda: self.stepLabel2.setText(&quot;Long-Running2 Step: 0&quot;)
)

if name == "main":

app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())

output, logging file:

Main Thread started at 1684097271.8644187 seconds on thread 140012231251776
main thread on : 140012231251776
Thread started at 1684097273.363497 seconds on thread 140012231251776
Worker started at 1684097273.3651009 seconds on thread 140010737583872
Thread2 started at 1684097275.1072826 seconds on thread 140012231251776
Worker started at 1684097275.1093683 seconds on thread 140010729191168
Worker finished @ 1684097278.3725977 seconds, 5.01 seconds later on thread 140010737583872
Worker finished @ 1684097280.1210995 seconds, 5.01 seconds later on thread 140010729191168
Thread started at 1684097284.508442 seconds on thread 140012231251776
Worker started at 1684097284.512356 seconds on thread 140010729191168
Thread2 started at 1684097285.267206 seconds on thread 140012231251776
Worker started at 1684097285.2695184 seconds on thread 140010737583872
Worker finished @ 1684097289.5649338 seconds, 5.05 seconds later on thread 140010729191168
Worker finished @ 1684097290.290237 seconds, 5.02 seconds later on thread 140010737583872


just press one of the two Long-Running Task to start a new thread.
Using `QThreadPool`  as per [https://www.pythonguis.com/tutorials/multithreading-pyqt-applications-qthreadpool/][2]

import logging

import threading

import sys

from time import sleep

import time

from PyQt5.QtCore import QObject, QThreadPool, pyqtSignal, pyqtSlot, Qt, QRunnable

from PyQt5.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)

Snip...

logger = logging.getLogger("test")
logger.setLevel(level=logging.DEBUG)

logFileFormatter = logging.Formatter(
fmt=f"%(levelname)s %(asctime)s (%(relativeCreated)d) \t F%(funcName)s L%(lineno)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
fileHandler = logging.FileHandler(filename='test.log' , mode = 'w+')
fileHandler.setFormatter(logFileFormatter)
fileHandler.setLevel(level=logging.DEBUG)

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)

logger.addHandler(fileHandler)
logger.addHandler(console)

Step 1: Create a worker class

class Worker(QRunnable):

def __init__(self, n):
super(Worker, self).__init__()
self.n = n
@pyqtSlot()
def run(self):
logger.debug(f&#39;Worker {self.n} started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
time.sleep(5)
logger.debug(f&#39;Worker {self.n} finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later on thread {threading.get_ident()}&#39;)

class Window(QMainWindow):

def __init__(self, parent=None):
super().__init__(parent)
self.clicksCount = 0
self.n = 1
self.threadpool = QThreadPool()
print(&quot;Multithreading with maximum %d threads&quot; % self.threadpool.maxThreadCount())
self.setupUi()
def setupUi(self):
self.setWindowTitle(&quot;Freezing GUI&quot;)
self.resize(300, 250)
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
# Create and connect widgets
self.clicksLabel = QLabel(&quot;Counting: 0 clicks&quot;, self)
self.clicksLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.stepLabel = QLabel(&quot;Long-Running Task: &quot; + str(self.n))
self.stepLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.countBtn = QPushButton(&quot;Click me!&quot;, self)
self.countBtn.clicked.connect(self.countClicks)
self.longRunningBtn = QPushButton(&quot;Long-Running Task!&quot;, self)
self.longRunningBtn.clicked.connect(lambda : self.runLongTask(self.n))
# Set the layout
layout = QVBoxLayout()
layout.addWidget(self.clicksLabel)
layout.addWidget(self.countBtn)
layout.addStretch()
layout.addWidget(self.stepLabel)
layout.addWidget(self.longRunningBtn)
self.centralWidget.setLayout(layout)   
print(&#39;main thread on : &#39;, threading.get_ident())
logger.debug(f&#39;Main Thread started at {(t1 := time.time())} seconds on thread {threading.get_ident()}&#39;)
def countClicks(self):
self.clicksCount += 1
self.clicksLabel.setText(f&quot;Counting: {self.clicksCount} clicks&quot;)
def runLongTask(self, n):
self.stepLabel.setText(f&quot;Long-Running Step: {n}&quot;)
worker = Worker(n)
self.threadpool.start(worker)
self.n = n +1

if name == "main":

app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())

output / logging file:

Main Thread started at 1684100478.011803 seconds on thread 140273846236992
Multithreading with maximum 2 threads
main thread on : 140273846236992
Worker 1 started at 1684100481.7684858 seconds on thread 140272195036928
Worker 2 started at 1684100482.0010214 seconds on thread 140272186644224
Worker 1 finished @ 1684100486.7727013 seconds, 5.0 seconds later on thread 140272195036928
Worker 3 started at 1684100486.7735689 seconds on thread 140272195036928
Worker 2 finished @ 1684100487.005124 seconds, 5.0 seconds later on thread 140272186644224
Worker 4 started at 1684100487.0061054 seconds on thread 140272186644224
Worker 3 finished @ 1684100491.7825654 seconds, 5.01 seconds later on thread 140272195036928
Worker 5 started at 1684100491.785199 seconds on thread 140272195036928
Worker 4 finished @ 1684100492.0294738 seconds, 5.02 seconds later on thread 140272186644224
Worker 5 finished @ 1684100496.834566 seconds, 5.05 seconds later on thread 140272195036928


just keep clicking the **Long-Running Task** button as long as you want, keep in mind that the number of threads are related to your CPU as per : `QThreadPool().maxThreadCount()` ; but in any case it will take care of the queque.
[1]: https://realpython.com/python-pyqt-qthread/
[2]: https://www.pythonguis.com/tutorials/multithreading-pyqt-applications-qthreadpool/
</details>

huangapple
  • 本文由 发表于 2023年5月14日 20:07:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/76247401.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定