QThreads в Pyqt5: это правильный перевод официальных документов QThread с C ++ на Python? - PullRequest
0 голосов
/ 25 мая 2018

Официальную документацию о том, как создать и использовать QThread, можно найти здесь: http://doc.qt.io/qt-5/qthread.html

Документация описывает два основных подхода: (1) подход «работник-объект» и (2) QThread Подкласс подход.
Я читал в нескольких статьях, что второй подход не подходит, поэтому давайте сосредоточимся на первом.


РЕДАКТИРОВАТЬ:
@ekhumoro указал мне на следующую интересную статью: https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html.Очевидно, что оба подхода (1) и (2) имеют свои достоинства:

Как правило:

Если вам не нужен цикл обработки событий в потоке, вы должны создать подкласс. Если вам нужен цикл обработки событий и обрабатывать сигналы и слоты в потоке, вам, возможно, не понадобится создавать подклассы.

Поскольку мне нужна какая-то связь междуПоток QApplication и новый QThread (и я считаю, что сигнальный слот является хорошим способом связи), я буду использовать подход «рабочий объект» .


1.Подход "рабочий-объект" в C ++

Я скопировал код C ++ подход "рабочий-объект" (из официальных документов Qt5 см. http://doc.qt.io/qt-5/qthread.html):

class Worker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(const QString &parameter) {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        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, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &);
signals:
    void operate(const QString &);
};


2. Подход рабочий-объект в Python

Я попытался перевести данный код C ++ в Python. Если у вас установлены Python 3.6 и PyQt5,Вы можете просто скопировать и вставить этот код и запустить его на своем компьютере. Он должен работать.

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

class Worker(QObject):

    resultReady = pyqtSignal(str)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    @pyqtSlot(str)
    def doWork(self, param):
        result = "hello world"
        print("foo bar")
        # ...here is the expensive or blocking operation... #
        self.resultReady.emit(result)


class Controller(QObject):

    operate = pyqtSignal(str)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # 1. Create 'workerThread' and 'worker' objects
        # ----------------------------------------------
        self.workerThread = QThread()
        self.worker = Worker()          # <- SEE NOTE(1)
        self.worker.moveToThread(self.workerThread)

        # 2. Connect all relevant signals
        # --------------------------------
        self.workerThread.finished.connect(self.worker.deleteLater)
        self.workerThread.finished.connect(lambda: print("workerThread finished."))  # <- SEE NOTE(2)
        self.operate.connect(self.worker.doWork)
        self.worker.resultReady.connect(self.handleResults)

        # 3. Start the thread
        # --------------------
        self.workerThread.start()

    def __del__(self):
        self.workerThread.quit()
        self.workerThread.wait()

    @pyqtSlot(str)
    def handleResults(self, param):
        print(param)
        # One way to end application
        # ---------------------------
        # global app      # <- SEE
        # app.exit()      #     NOTE(3)

        # Another way to end application
        # -------------------------------
        self.workerThread.quit()   # <- SEE NOTE(4)
        self.thread().quit()


if __name__ == '__main__':
    app = QCoreApplication([])
    controller = Controller()
    controller.operate.emit("foo")      # <- SEE NOTE(5)
    sys.exit(app.exec_())

ПРИМЕЧАНИЕ (1):
Изначально я реализовалworker переменная как локальная переменная в конструкторе. Я буквально переводил образец C ++ в Python, и эта переменная также является локальной переменной в образце C ++.
Как вы можете видеть в комментарии @pschill, этоЛокальная переменная была собрана сборщиком мусора, и поэтому я не смог запустить поток. После внесения изменений я получил ожидаемый результат.

ПРИМЕЧАНИЕ (2):
Я добавил эту строку, чтобы точно знать, когда заканчивается workerThread.

ПРИМЕЧАНИЕ (3):
Очевидно, мне нужно добавить эти две строки кода global app и app.exit() в слот handleResults(..).Спасибо @Matic за указание на это!

ПРИМЕЧАНИЕ (4):
Я обнаружил (в некоторых документах) этот подход для завершения приложения,Первая кодовая строка завершает workerThread (уничтожая цикл обработки событий).Вторая кодовая строка завершает mainThread (также уничтожая цикл обработки событий).

ПРИМЕЧАНИЕ (5):
При запуске кода вКонсоль Windows, ничего не произошло (просто зависло).По совету @pschill (см. Его комментарий ниже) я добавил эту строку кода, чтобы обеспечить вызов функции doWork().


3.Мои вопросы

  1. Прежде всего, я хотел бы знать, правильный ли мой перевод с C ++ на Python.Пожалуйста, покажите мне, где я допустил ошибки (если вы их обнаружите).

  2. Добавление строк кода global app и app.exit() в слот handleResults(..) устраняет проблему зависания.Но что именно происходит на заднем плане?Эти строки кода убивают рабочий поток?Или основной поток QApplication?

  3. Есть ли способ уничтожить рабочий поток без уничтожения основного потока QApplication?


4.Некоторые ответы

1.Все еще не уверен ..


2. Я считаю, что app.exit() убивает основной поток, который, в свою очередь, убивает рабочий поток, потому что он имеет тип deamon .Я обнаружил, что рабочий поток имеет тип deamon , потому что я вставил кодовую строку print(threading.current_thread()) в функцию doWork(..).На нем напечатано <_DummyThread(Dummy-1, started daemon 9812)>.Когда программа закрывается, все потоки демона автоматически уничтожаются.


3. Да, я нашел способ!Функция QThread::quit() - ваш друг.Официальные документы говорят об этом:

void QThread::quit()
Сообщает о выходе из цикла потока с кодом возврата 0 (успех).Эквивалентно вызову QThread::exit(0).
Эта функция ничего не делает, если у потока нет цикла обработки событий.
[http://doc.qt.io/qt-5/qthread.html#quit]

Итак, моя функция handleResults(..) сейчасвыглядит так:

    @pyqtSlot(str)
    def handleResults(self, param):
        print(param)
        self.workerThread.quit()  # Kill the worker thread
        self.thread().quit()      # Kill the main thread

Я проверил уничтожение рабочего потока, вставив эту строку в конструктор Controller(..):

    self.workerThread.finished.connect(lambda: print("workerThread finished."))

Я действительно получил строкураспечатан как ожидалось.Я также пытался проверить уничтожение основного потока аналогичным образом:

    self.thread().finished.connect(lambda: print("mainThread finished."))

К сожалению, эта строка не распечатывается.Почему?


Настоящим я предоставляю свои текущие настройки системы:
> Qt5 (QT_VERSION_STR = 5.10.1)
> PyQt5 (PYQT_VERSION_STR = 5.10.1)
> Python 3.6.3
> Windows 10, 64-битная

1 Ответ

0 голосов
/ 25 мая 2018

Ваше приложение-пример Python должно как-то завершиться, в противном случае оно просто остается там после инициализации объекта Controller.

Самое простое - изменить функцию handleResults в вашем примере на:

@pyqtSlot(str)
def handleResults(self, param):
    print(param)
    global app
    app.exit()

Надеюсь, это поможет.

...