PySide2:如何使装饰槽在其工作线程上执行?

Posted

技术标签:

【中文标题】PySide2:如何使装饰槽在其工作线程上执行?【英文标题】:PySide2: How to make a decorated slot execute on its worker thread? 【发布时间】:2020-03-20 15:04:33 【问题描述】:

使用 Python 3.7 和 PySide2,我创建了一个 worker object on a dedicated QThread 来执行一个长时间运行的函数。这在下面的代码中进行了说明。

import threading
from time import sleep
from PySide2.QtCore import QObject, QThread, Signal, Slot
from PySide2.QtWidgets import QApplication

class Main(QObject):   
    signal_for_function = Signal()

    def __init__(self):
        print('The main thread is "%s"' % threading.current_thread().name)
        super().__init__()
        self.thread = QThread(self)
        self.worker = Worker()
        self.worker.moveToThread(self.thread)
        self.thread.start()
        self.signal_for_function.connect(self.worker.some_function)

def some_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

class Worker(QObject):
    # @some_decorator
    def some_function(self):
        print('some_function is running on thread "%s"' % threading.current_thread().name)

app = QApplication()
m = Main()
m.signal_for_function.emit()

sleep(0.100)
m.thread.quit()
m.thread.wait()

如果我在没有装饰器的情况下使用 some_function,我会按预期得到:

The main thread is "MainThread"
some_function is running on thread "Dummy-1"

但是,如果我应用装饰器(即取消注释“@some_decorator”),我会得到:

The main thread is "MainThread"
some_function is running on thread "MainThread"

为什么会发生这种情况,如何让装饰函数按照我的意图在工作线程上运行?

【问题讨论】:

经过一番研究,这里有一些相关的问题:***.com/questions/43937897/…和***.com/questions/23317195/… 【参考方案1】:

解决方案:

你必须使用@functools.wrap:

import functools
# ...

def some_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

输出:

The main thread is "MainThread"
some_function is running on thread "Dummy-1"

说明:

要分析使用@functools.wrap与不使用的区别,则必须使用以下代码:

def some_decorator(func):
    print(func.__name__, func.__module__, func.__doc__, func.__dict__)

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs) 
    print(wrapper.__name__, wrapper.__module__, wrapper.__doc__, wrapper.__dict__)
    return wrapper

通过删除@functools.wrap,您应该得到以下信息:

some_function __main__ None 
wrapper __main__ None 

通过不删除@functools.wrap,您应该得到以下信息:

some_function __main__ None 
some_function __main__ None '__wrapped__': <function Worker.some_function at 0x7f610d926a60>

主要区别在于__name__,在@functools.wrap 的情况下,它使包装函数与“func”同名,这有什么区别?它用于识别函数是否属于 Worker 类,即在创建 Worker 类时,会创建一个存储方法、属性等的字典,但是当信号调用 some_function 时,它会返回包装器名称为“wrapper”的名称不在 Worker 的字典中,但在使用 @functools.wrapper 的情况下,调用 some_function 然后返回名称为“some_function”的 wrapper,导致 Worker 对象调用它。

【讨论】:

该修复程序适用于这种特定情况,但我不确定您的解释是否完全正确。如果您在 Worker 的 __init__ 构造函数中定义 some_functionsome_function 将在 Worker 的字典中并且具有正确的名称,但它仍然不会在工作线程上执行。为什么? @derren 1) 在 Worker 的 __init__ 构造函数中定义 some_function 是什么意思?我在你指出的内容中明确表示,2) PySide2 创建一个字典创建实例时的模式(在 _new_ 方法中调用 _init_ 之前的一步)。但一般来说,您应该始终使用 functools.wrap 以便装饰器(最终是方法包装器)的行为与函数相同。

以上是关于PySide2:如何使装饰槽在其工作线程上执行?的主要内容,如果未能解决你的问题,请参考以下文章

Pyside2信号连接 定义方法的方式

如何正确退出 Pyside2 多线程 GUI 应用程序?

如何使后台工作线程之间的事件在自己的上下文中执行

装饰 MKPinAnnotation

PySide2 和 Matplotlib:如何让 MatPlotLib 在单独的进程中运行? ..因为它不能在单独的线程中运行

Choreographer: Skipped frames : 应用程序可能在其主线程上做太多工作