PySide: method is not executed in thread context if method is invoked via lambda
我有一个
现在我叫它的
- 直接调用该方法,该方法在其对象所在的线程中执行。
- 如果我使用lambda调用该方法,则该方法在主线程中执行
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | from PySide.QtCore import * from PySide.QtGui import * import sys class Worker(QObject): def __init__(self): super().__init__() def work(self): print(self.thread().currentThread()) class Example(QWidget): def __init__(self): super().__init__() self.btnInThread = QPushButton('in thread') self.btnNotInThread = QPushButton('not in thread') layout = QVBoxLayout() layout.addWidget(self.btnInThread) layout.addWidget(self.btnNotInThread) self.setLayout(layout) self.worker = Worker() self.Thread = QThread() self.worker.moveToThread(self.Thread) self.Thread.start() self.btnInThread.clicked.connect(self.worker.work) self.btnNotInThread.clicked.connect(lambda: self.worker.work()) self.show() print('{0} <- Main Thread'.format(self.thread().currentThread())) def main(): app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_()) if __name__ == '__main__': main() |
我做了过多的测试(请参见代码段中的代码),但是我完全不知道发生了什么。
所以我的问题是:
为什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | from PySide.QtCore import * from PySide.QtGui import * from time import sleep import functools import sys class Worker(QObject): def __init__(self): super().__init__() def work(self, name='Nothing'): print('Thread ID: {1} - {0} start'.format(name, QThread.currentThreadId())) sleep(1) print('##### End {0}'.format(name)) class HackPushButton(QPushButton): clicked_with_arg = Signal(str) def __init__(self, *args): super().__init__(*args) self.argument = None self.clicked.connect(lambda: self.clicked_with_arg.emit(self.argument)) class Example(QWidget): def __init__(self): super().__init__() self.buttonWithoutLambda = QPushButton('[Works] Call work() without arguments and without lambda') self.buttonWithLambda = QPushButton('[Blocks] Call work() with arguments and with lambda') self.buttonWithFunctools = QPushButton('[Blocks] Call work() with arguments and with functools') self.buttonWithHelperFunctionWithArgument = QPushButton('[Blocks] Call work() with arguments and with helper function') self.buttonWithHelperFunctionWithoutArgument = QPushButton('[Blocks] Call work() without arguments and with helper function') self.buttonWithHack = HackPushButton('[Works] Call work() with arguments via dirty hack') layout = QVBoxLayout() layout.addWidget(self.buttonWithoutLambda) layout.addWidget(self.buttonWithLambda) layout.addWidget(self.buttonWithFunctools) layout.addWidget(self.buttonWithHelperFunctionWithArgument) layout.addWidget(self.buttonWithHelperFunctionWithoutArgument) layout.addWidget(self.buttonWithHack) self.setLayout(layout) self.Worker = Worker() self.Thread = QThread() self.Worker.moveToThread(self.Thread) self.Thread.start() # Doesn't block GUI self.buttonWithoutLambda.clicked.connect(self.Worker.work) # Blocks GUI self.buttonWithLambda.clicked.connect(lambda: self.Worker.work('Lambda')) # Blocks GUI self.buttonWithFunctools.clicked.connect(functools.partial(self.Worker.work, 'Functools')) # Blocks GUI self.helperFunctionArgument = 'Helper function without arguments' self.buttonWithHelperFunctionWithArgument.clicked.connect(self.helperFunctionWithArgument) # Blocks GUI self.buttonWithHelperFunctionWithoutArgument.clicked.connect(self.helperFunctionWithoutArgument) # Doesn't block GUI self.buttonWithHack.argument = 'Hack' self.buttonWithHack.clicked_with_arg.connect(self.Worker.work) print('Thread ID: {0}'.format(QThread.currentThreadId())) self.show() def helperFunctionWithArgument(self): self.Worker.work(self.helperFunctionArgument) def helperFunctionWithoutArgument(self): self.Worker.work() app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_()) |
我想我有一个答案。
我尝试对您进行编码,并更改了
我通读了文档http://doc.qt.io/qt-4.8/threads-qobject.html #signals-and-slots-across-threads。
Queued Connection The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread.
因此,我相信lambda在主线程中运行,因为lambda正在创建一个新函数。新的lambda函数不是插槽,而是存在于主线程中。 lambda函数接收者的线程是主线程。同时,
我知道这很不方便,因为lambda很有用用于将参数传递给函数。
使用信号映射器传递值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | from PySide import QtCore from PySide import QtGui import sys import time def create_map(obj, func, args=None): """Create a signal mapper to associate a value with a function. Args: obj (QObject): Object to map the value to with the signal mapper func (function): Function to run when the signal mapper.map function is called. args (tuple)[None]: Arguments you want to pass to the function. Returns: map_callback (function): Map function to connect to a signal. mapper (QSignalMapper): You may need to keep a reference of the signal mapper object. """ mapper = QtCore.QSignalMapper() mapper.setMapping(obj, args) mapper.mapped.connect(func) return mapper.map, mapper class Worker(QtCore.QObject): def __init__(self): super().__init__() def work(self, value=0): print(self.thread().currentThread()) time.sleep(2) print("end", value) class Example(QtGui.QWidget): def __init__(self): super().__init__() self.btnInThread = QtGui.QPushButton('in thread') self.btnNotInThread = QtGui.QPushButton('not in thread') layout = QtGui.QVBoxLayout() layout.addWidget(self.btnInThread) layout.addWidget(self.btnNotInThread) self.setLayout(layout) self.worker = Worker() self.Thread = QtCore.QThread() self.worker.moveToThread(self.Thread) self.Thread.start() self.btnInThread.clicked.connect(self.worker.work) # Use a signal mapper # self.mapper = QtCore.QSignalMapper() # self.mapper.setMapping(self.btnNotInThread, 1) # self.mapper.mapped.connect(self.worker.work) # self.btnNotInThread.clicked.connect(self.mapper.map) # Alternative mapper method from above callback, self.mapper = create_map(self.btnNotInThread, self.worker.work, 1) self.btnNotInThread.clicked.connect(callback) self.show() print('{0} <- Main Thread'.format(self.thread().currentThread())) def main(): app = QtGui.QApplication(sys.argv) ex = Example() sys.exit(app.exec_()) if __name__ == '__main__': main() |