`@pyqtSlot()` 在嵌套函数中是否有相同的效果?

3

1. 简介

我正在使用 Python 3.7 中的 PyQt5 开发一个多线程应用程序,其中我依赖于 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()。单次计时器挂钩到此插槽并触发事件。由于my_thread中的Qt事件循环,插槽实际上在其中执行其代码。

 

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. 附注:如何钩住内部函数

你可能会想知道如何钩住内部函数。好吧,请考虑以下内容:

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()并触发。假设Worker()对象已分配给my_thread,那么内部函数是否会在my_thread中执行?
1个回答

2
在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调用的,因此它也将在该线程中执行。

示例3:

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

在这种情况下,some_function是间接调用的,并且属于Worker上下文,因此它将在辅助线程上执行,因此some_inner_function将在辅助线程上执行。
总之,some_inner_function将在与执行some_function相同的线程上运行,即使直接或间接调用它,因为它没有上下文。

感谢@eyllanesc的出色回答!我已经捐了一些钱,所以你可以买几杯咖啡和蛋糕来庆祝一下;-) - K.Mulier
嗨@eyllanesc,你能否请看一下我的最新PyQt问题:https://dev59.com/pLPma4cB1Zd3GeqPxuFI? - K.Mulier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接