Оказывает ли `@pyqtSlot ()` такой же эффект на вложенную функцию? - PullRequest
2 голосов
/ 11 мая 2019

1.Введение

Я работаю с PyQt5 в Python 3.7 над многопоточным приложением, для которого я полагаюсь на QThread.

Теперь предположим, что у меня есть класс, производный от QObject,В этом классе я определяю функцию, помеченную @pyqtSlot:

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

class Worker(QObject):
    def __init__(self):
        super().__init__()
        return

    @pyqtSlot()
    def some_function(self):
        ...
        return

. В другом коде я создаю экземпляр Worker() и перемещаю его в новый поток, например:

my_thread = QThread()
my_worker = Worker()
my_worker.moveToThread(my_thread)
my_thread.start()

QTimer.singleShot(100, my_worker.some_function)
return

Обычно some_function() теперь должен запускаться в my_thread.Это потому что:

  1. Я выдвинул объект Worker() на my_thread.
  2. Когда я дал команду my_thread, чтобы начать, я фактически родил новый цикл событий Qt в этом потоке.my_worker объект живет в этом цикле событий.Все его слоты могут получать события, которые выполняются в этом цикле событий.
  3. some_function() правильно помечается как @pyqtSlot().Таймер одиночного выстрела зацепляет этот слот и запускает событие.Благодаря циклу событий Qt в my_thread слот эффективно выполняет свой код в my_thread.


2.Мой вопрос

Мой вопрос касается вложенных функций (также называемых «внутренними функциями»).Учтите это:

class Worker(QObject):
    def __init__(self):
        super().__init__()
        return

    def some_function(self):
        ...
        @pyqtSlot()
        def some_inner_function():
            ...
            return
        return

Как видите, some_inner_function() аннотируется как @pyqtSlot.Будет ли его код также выполняться в потоке, в котором живет Worker() -объект?


3.Sidenote: как подключить внутреннюю функцию

Вы можете спросить, как я мог подключить что-то к внутренней функции.Хорошо, рассмотрим следующее:

class Worker(QObject):
    def __init__(self):
        super().__init__()
        return

    def some_function(self):
        @pyqtSlot()
        def some_inner_function():
            # Will this code run in `my_thread`?
            ...
            return
        # some_function() will run in the main thread if
        # it is called directly from the main thread.
        QTimer.singleShot(100, some_inner_function)
        return

Если вы вызываете some_function() непосредственно из основного потока, он (к сожалению) будет работать в основном потоке.Без правильного использования механизма слотов сигналов вы не сможете переключать потоки.

Таймер одиночного выстрела внутри some_function() захватывает some_inner_function() и срабатывает.Будет ли внутренняя функция выполняться в my_thread (при условии, что Worker() -объекту был присвоен my_thread)?

1 Ответ

2 голосов
/ 12 мая 2019

В Qt есть следующие правила о том, что:

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

  2. Если вызываемый объект вызывается косвенно (через сигналы qt, QTimer::singleShot() или QMetaObject::invokeMethod()), он будет выполнен в контексте, к которому он принадлежит.И контекст ссылается на QObject.

  3. Если вызываемый объект не принадлежит контексту, он будет выполнен в потоке, в котором он был вызван косвенно.

  4. Внутренние функции не принадлежат контексту, поэтому даже если он вызывается прямо или косвенно, он будет выполняться в потоке, в котором он был вызван.

На основевыше, давайте проанализируем несколько случаев в качестве упражнения для проверки предыдущих правил:

Пример 1

from PyQt5 import QtCore
import threading


class Worker(QtCore.QObject):
    def some_function(self):
        def some_inner_function():
            print("inner thread", threading.get_ident())
            QtCore.QThread.sleep(1)

        print("worker thread", threading.get_ident())
        some_inner_function()


if __name__ == "__main__":
    import sys

    app = QtCore.QCoreApplication(sys.argv)
    thread = QtCore.QThread()
    thread.start()
    my_worker = Worker()
    my_worker.moveToThread(thread)
    my_worker.some_function()
    print("main thread", threading.get_ident())
    sys.exit(app.exec_())

Вывод:

worker thread 140678349403776
inner thread 140678349403776
main thread 140678349403776

В этом случае правило 1 выполняется, потому что все вызываемые объекты вызываются напрямую.

Пример 2

from PyQt5 import QtCore
import threading


class Worker(QtCore.QObject):
    def some_function(self):
        @QtCore.pyqtSlot()
        def some_inner_function():
            print("inner thread", threading.get_ident())
            QtCore.QThread.sleep(1)

        print("worker thread", threading.get_ident())
        QtCore.QTimer.singleShot(0, some_inner_function)


if __name__ == "__main__":
    import sys

    app = QtCore.QCoreApplication(sys.argv)
    thread = QtCore.QThread()
    thread.start()
    my_worker = Worker()
    my_worker.moveToThread(thread)
    my_worker.some_function()
    print("main thread", threading.get_ident())
    sys.exit(app.exec_())

Вывод:

worker thread 139721158932096
main thread 139721158932096
inner thread 139721158932096

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

Example3:

from PyQt5 import QtCore
import threading


class Worker(QtCore.QObject):
    def some_function(self):
        @QtCore.pyqtSlot()
        def some_inner_function():
            print("inner thread", threading.get_ident())
            QtCore.QThread.sleep(1)

        print("worker thread", threading.get_ident())
        QtCore.QTimer.singleShot(0, some_inner_function)


if __name__ == "__main__":
    import sys

    app = QtCore.QCoreApplication(sys.argv)
    thread = QtCore.QThread()
    thread.start()
    my_worker = Worker()
    my_worker.moveToThread(thread)
    QtCore.QTimer.singleShot(0, my_worker.some_function)
    print("main thread", threading.get_ident())
    sys.exit(app.exec_())

Выход:

main thread 139934436517504
worker thread 139934378075904
inner thread 139934378075904

In thВ этом случае some_function вызывается косвенно и принадлежит контексту Worker, поэтому он будет выполняться во вторичном потоке, поэтому функция some_inner_function будет выполняться во вторичном потоке.


В заключение some_inner_function будет выполняться в том жепоток как some_function был выполнен, даже вызывать его прямо или косвенно, так как он не имеет контекста.

...