我读了这篇文章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,
答案 0 :(得分:11)
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的信号/插槽创建了一种执行多线程编程的极其强大的方法。小心不要让你被推到工作线程的QObject徘徊......
http://ilearnstuff.blogspot.co.uk/2012/09/qthread-best-practices-when-qthread.html
答案 1 :(得分:4)
我试图在我的应用程序中使用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")
答案 2 :(得分:0)
我已经尝试了@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
在工作线程__init__
中进行PS连接显然等效于在主线程中在moveToThread
之前进行连接。
(在PyQt5,win64下测试)。