web-dev-qa-db-ja.com

moveToThread()を使用してpyqtでQThreadを正しく使用するにはどうすればよいですか?

私はこの記事を読みました 実際にQThreadsを実際に使用する方法;完全な説明 、サブクラスqthreadの代わりに、run()を再実装すると、moveToThread(moveToThread()を使用してQObjectをQThreadインスタンスにプッシュする必要がありますQThread *)

これがc ++の例ですが、それをpythonコードに変換する方法がわかりません。

class Worker : public QObject
 {
     Q_OBJECT
     QThread workerThread;

 public slots:
     void doWork(const QString &parameter) {
         // ...
         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)

編集:2年後...私は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,
17
Shuman

QThreadのデフォルトのrun()実装は、次と同等のイベントループを実行します。

_class GenericThread(QThread):
    def run(self, *args):
        self.exec_()
_

イベントループの重要な点は、オブジェクト所有者スレッドがスロットでイベントを受信できるようにすることです。イベントは実行されますそのスレッド内。これらのオブジェクトは単なるQObjectであり、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

13
qris

アプリケーションでqrisの例を使用しようとしましたが、メインスレッドでコードを実行し続けました。それは彼がrunと呼ぶことを宣言したsignalの方法です!

基本的に、オブジェクトのコンストラクターで接続すると、2つのオブジェクト間に接続が存在しますメインスレッド内-QObjectのプロパティはそれらを作成したスレッドに属しているため。 QObjectを新しいスレッドに移動すると、接続は一緒に移動しません。シグナルをrun関数に接続している行を削除し、接続しますワーカーを新しいスレッドに移動した後!

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")
5
Matthew Runchey

@qrisと@MatthewRuncheyの両方のアプローチを試しました。

あり@pyqtSlotデコレータQtは、シグナルが発行されるときにワーカーインスタンスの「場所」をチェックします。moveToThreadがワーカースレッドのスロットを実行した後、moveToThreadがシグナルを発行する前に接続が確立された場合でも。

なし@pyqtSlotデコレータQtは、接続が確立された瞬間にワーカーインスタンスの「場所」をフリーズします。それがmoveToThreadの前だった場合、メインスレッドにバインドされ、スロットコードはメインで実行され続けます。 moveToThread呼び出しの後に信号が発行された場合でもスレッド。

確立された接続aftermoveToThreadは、どちらの場合もワーカースレッドを実行するスロットをバインドします。

コード:

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ワーカーでの接続__init__メソッドは、メインスレッドでmoveToThreadの前に接続することと明らかに同等です。

(PyQt5、win64でテスト済み)。

2