我读了这篇文章How To Really, Truly Use QThreads; The Full Explanation,它说而不是子类qthread,并重新实现run(),应该使用moveToThread使用moveToThread(QThread *)将QObject推送到QThread实例
这是c ++示例,但我不知道如何将其转换为python代码。
class Worker : public QObject
{
Q_OBJECT
QThread workerThread;
public slots:
void doWork(const QString ¶meter) {
// ...
emit resultReady(result);
}
signals:
void resultReady(const QString &result);
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(this, SIGNAL(operate(QString)), worker, SLOT(doWork(QString)));
connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
signals:
void operate(const QString &);
};
QThread* thread = new QThread;
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
connect(thread, SIGNAL(started()), worker, SLOT(process()));
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
我一直在使用这种方法来生成qthread,但正如你所看到的,它使用了不推荐的方式。如何重新编写它以使用首选方法?
class GenericThread(QThread):
def __init__(self, function, *args, **kwargs):
QThread.__init__(self)
# super(GenericThread, self).__init__()
self.function = function
self.args = args
self.kwargs = kwargs
def __del__(self):
self.wait()
def run(self, *args):
self.function(*self.args, **self.kwargs)
编辑:两年后......我尝试了qris的代码,它的工作原理和不同的线程
import sys
import time
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import pyqtSignal, pyqtSlot
import threading
def logthread(caller):
print('%-25s: %s, %s,' % (caller, threading.current_thread().name,
threading.current_thread().ident))
class MyApp(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(300, 300, 280, 600)
self.setWindowTitle('using threads')
self.layout = QtGui.QVBoxLayout(self)
self.testButton = QtGui.QPushButton("QThread")
self.testButton.released.connect(self.test)
self.listwidget = QtGui.QListWidget(self)
self.layout.addWidget(self.testButton)
self.layout.addWidget(self.listwidget)
self.threadPool = []
logthread('mainwin.__init__')
def add(self, text):
""" Add item to list widget """
logthread('mainwin.add')
self.listwidget.addItem(text)
self.listwidget.sortItems()
def addBatch(self, text="test", iters=6, delay=0.3):
""" Add several items to list widget """
logthread('mainwin.addBatch')
for i in range(iters):
time.sleep(delay) # artificial time delay
self.add(text+" "+str(i))
def test(self):
my_thread = QtCore.QThread()
my_thread.start()
# This causes my_worker.run() to eventually execute in my_thread:
my_worker = GenericWorker(self.addBatch)
my_worker.moveToThread(my_thread)
my_worker.start.emit("hello")
# my_worker.finished.connect(self.xxx)
self.threadPool.append(my_thread)
self.my_worker = my_worker
class GenericWorker(QtCore.QObject):
start = pyqtSignal(str)
finished = pyqtSignal()
def __init__(self, function, *args, **kwargs):
super(GenericWorker, self).__init__()
logthread('GenericWorker.__init__')
self.function = function
self.args = args
self.kwargs = kwargs
self.start.connect(self.run)
@pyqtSlot()
def run(self, *args, **kwargs):
logthread('GenericWorker.run')
self.function(*self.args, **self.kwargs)
self.finished.emit()
# run
app = QtGui.QApplication(sys.argv)
test = MyApp()
test.show()
app.exec_()
输出是:
mainwin.__init__ : MainThread, 140221684574016,
GenericWorker.__init__ : MainThread, 140221684574016,
GenericWorker.run : Dummy-1, 140221265458944,
mainwin.addBatch : Dummy-1, 140221265458944,
mainwin.add : Dummy-1, 140221265458944,
mainwin.add : Dummy-1, 140221265458944,
mainwin.add : Dummy-1, 140221265458944,
mainwin.add : Dummy-1, 140221265458944,
mainwin.add : Dummy-1, 140221265458944,
mainwin.add : Dummy-1, 140221265458944,
QThread中的默认run()实现为您运行一个事件循环,相当于:
class GenericThread(QThread):
def run(self, *args):
self.exec_()
关于事件循环的重要一点是它允许线程拥有的对象在其插槽上接收事件,这些事件将在该线程中执行。那些对象只是QObjects,而不是QThreads。
重要提示:QThread对象不属于自己的线程!它是在主线程上创建的并且存在于那里。除了run方法之外,它的所有代码都在主线程中执行。
所以你应该能够做到这一点:
class GenericWorker(QObject):
def __init__(self, function, *args, **kwargs):
super(GenericWorker, self).__init__()
self.function = function
self.args = args
self.kwargs = kwargs
self.start.connect(self.run)
start = pyqtSignal(str)
@pyqtSlot
def run(self, some_string_arg):
self.function(*self.args, **self.kwargs)
my_thread = QThread()
my_thread.start()
# This causes my_worker.run() to eventually execute in my_thread:
my_worker = GenericWorker(...)
my_worker.moveToThread(my_thread)
my_worker.start.emit("hello")
另外,请仔细考虑self.function
的结果会发生什么,目前已被丢弃。您可以在接收结果的GenericWorker
上声明另一个信号,并在完成时让run()
方法发出该信号,并将结果传递给它。
一旦你掌握了它,并意识到你没有,也不应该继承QThread,生活变得更加简单直接。简单地说,永远不要在QThread中工作。你几乎不需要覆盖run。对于大多数用例,设置与QObject到QThread的正确关联并使用QT的信号/插槽创建了一种非常强大的多线程编程方法。小心不要让你推到工作线程的QObjects徘徊......
http://ilearnstuff.blogspot.co.uk/2012/09/qthread-best-practices-when-qthread.html
我试图在我的应用程序中使用qris的示例,但仍然让我的代码在我的主线程中运行!这是他宣布呼叫的信号运行的方式!
基本上,当您在对象的构造函数中连接它时,连接将存在于主线程中的两个对象之间 - 因为QObject的属性属于创建它们的线程。将QObject移动到新线程时,连接不会随您移动。拿走将信号连接到run函数的行,并在将worker移动到新线程后连接它!
qris回答的相关变化:
class GenericWorker(QObject):
def __init__(self, function, *args, **kwargs):
super(GenericWorker, self).__init__()
self.function = function
self.args = args
self.kwargs = kwargs
start = pyqtSignal(str)
@pyqtSlot
def run(self, some_string_arg):
self.function(*self.args, **self.kwargs)
my_thread = QThread()
my_thread.start()
# This causes my_worker.run() to eventually execute in my_thread:
my_worker = GenericWorker(...)
my_worker.moveToThread(my_thread)
my_worker.start.connect(my_worker.run) # <---- Like this instead
my_worker.start.emit("hello")
我已经尝试了@qris和@MatthewRunchey方法。
使用@pyqtSlot
装饰器Qt在发出信号时检查工作者实例的“位置”:即使连接是在moveToThread
发出信号之后moveToThread
执行工作线程中的插槽。
没有@pyqtSlot
装饰器Qt在建立连接时冻结工人实例的“位置”:如果它在moveToThread
之前,它被绑定到主线程,并且插槽代码在主线程中继续执行,即使moveToThread
呼叫后发出信号。
在两种情况下,在moveToThread
之后建立的连接绑定槽以执行工作线程。
码:
from PyQt5.QtCore import (QCoreApplication, QObject, QRunnable, QThread,
QThreadPool, pyqtSignal, pyqtSlot)
class Worker(QObject):
def __init__(self):
super(Worker, self).__init__()
# self.call_f1.connect(self.f1)
# self.call_f2.connect(self.f2)
call_f1 = pyqtSignal()
call_f2 = pyqtSignal()
@pyqtSlot()
def f1(self):
print('f1', threading.get_ident())
@pyqtSlot()
def f2(self):
print('f2', threading.get_ident())
app = QCoreApplication([])
print('main', threading.get_ident())
my_thread = QThread()
my_thread.start()
my_worker = Worker()
my_worker.call_f1.connect(my_worker.f1)
my_worker.call_f1.emit()
my_worker.moveToThread(my_thread)
my_worker.call_f2.connect(my_worker.f2)
my_worker.call_f1.emit()
my_worker.call_f2.emit()
sys.exit(app.exec_())
有装饰:
main 18708
f1 18708
f1 20156
f2 20156
没有装饰者:
main 5520
f1 5520
f1 5520
f2 11472
PS在worker __init__
方法中连接显然相当于在主线程中连接moveToThread
之前。
(在PyQt5下测试,win64)。