2013-12-02 7 views
10

я прочитал эту статью How To Really, Truly Use QThreads; The Full Explanation, он говорит, что вместо подкласса QThread, и переопределение Run(), следует использовать moveToThread нажать QObject на QThread экземпляр с помощью moveToThread (QThread *)Как правильно использовать QThread в pyqt с moveToThread()?

здесь 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) 

редактировать: два года спустя ... я попробовал код 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, 
+3

Я удивлен, что этот вопрос был без ответа/раскомментировать так долго. В документе [docs] (http://pyqt.sourceforge.net/Docs/PyQt4/qobject.html#moveToThread) четко указано, что использование 'moveToThread (thread)' является предпочтительным способом, но все же код примера, который мне удалось найти подклассы 'QThread.run()' и поместить там работу. Было бы здорово, если бы мы могли увидеть пример или шаблон использования. – jpcgt

+0

Вот статья, которая многое помогла при реализации моего первого потока: https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation. Как вы сказали, это не рекомендуемая практика для подкласса QThread, даже если есть множество учебников, которые делают это. – Plouff

+0

Правильный ли последний выбор в вопросе? mainwin.add - это код, который добавляет элемент в listwidget, но, очевидно, из распечатанного вывода, мы можем видеть, что mainwin.add выполняется в потоке?!? –

ответ

9

по умолчанию на вращение () в QThread запускает цикл событий для вас, эквивалент:

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

Важным элементом цикла событий является то, что он позволяет объектам принадлежать потоку для получения событий на своих слотах, который будет выполнен в этой теме. Эти объекты - это только QObjects, а не QThreads.

Важное примечание: объект QThread не принадлежит к своей собственной нитью! Он был создан на главной теме и живет там. Помимо его метода запуска, весь его код выполняется в основном потоке.

Таким образом, вы должны быть в состоянии сделать это:

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. Вам почти не нужно отменять запуск. Для большинства случаев использование правильных ассоциаций с QObject для QThread и использование сигналов/слотов QT создает чрезвычайно мощный способ выполнения многопоточного программирования. Только будьте осторожны, чтобы не позволить объекты QObject вы подтолкнули к вашим рабочим потокам торчать ...

http://ilearnstuff.blogspot.co.uk/2012/09/qthread-best-practices-when-qthread.html

+1

Интересно, что декоратор @pyqtSlot для метода запуска здесь абсолютно важен; при выходе из него (что является «законным» в PyQt) в этом случае приведет к, казалось бы, серийному исполнению. Протестировано с помощью python 2.7.3. – TimO

+1

Важное примечание: убедитесь, что 'my_thread' и' my_worker' хранятся, если вы создаете их в методе, иначе сборщик мусора будет уничтожать их, а рабочий не будет запущен. – jpyams

3

Я пытался использовать пример qris в моем приложении, но продолжал иметь свой код, выполняемый в моя основная нить! Это сигнал , который он объявил, чтобы вызвать run!

В основном, когда вы подключаете его в конструкторе объекта, соединение будет существовать между двумя объектами в основном потоке - поскольку свойства QObject принадлежат потоку, который их создал.Когда вы перемещаете QObject в свой новый поток, соединение не перемещается с вами. Уберите линию, которая соединяет ваш сигнал с функцией запуска, и соедините его после того, как вы переместите работника в новый поток!

Соответствующее изменение от ответа qris'S:

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") 
+0

Я попробовал пример кода @qris, он работает, как можно Я говорю, что они все еще в одном проходе? Я обновил свой вопрос с помощью изменений – Shuman

Смежные вопросы