PySide:如果通过 lambda 调用方法,则方法不会在线程上下文中执行
PySide: method is not executed in thread context if method is invoked via lambda
我有一个 Worker
对象并使用它的方法 moveToThread
将它放在一个线程中。
现在我调用它的work
方法:
- 如果我直接调用该方法,它会在其对象所在的线程中执行
- 如果我使用 lambda 调用该方法,该方法将在主线程中执行
示例:
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()
我做了过多的测试(参见片段中的代码),但我完全不知道发生了什么。
所以我的问题是:
为什么work
在主线程中执行,而不是在lambda
调用时在其对象所在的线程中执行?最重要的是,如果我想调用 Worker
的方法需要参数,而我无法节省 lambda
?
,我该怎么办
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_())
我想我有答案了。
我尝试了您的代码,并更改了 connect
方法以使用 QtCore.Qt.QueuedConnection
和 QtCore.Qt.DirectConnection
(connect(self.worker.work, Qt.QueuedConnection)
)。直接连接使两个函数以相同的方式(lambda 的工作方式)在主线程中都 运行 工作。但是,队列连接使它们的工作方式不同。 lambda 函数仍在主线程中 运行s,而 worker 函数在单独的线程中调用 运行s。注意:如果不在 connect
中给出参数,您将使用 AutoConnection,它将使用 QueuedConnection。
我通读了文档 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 运行ning 在主线程中,因为 lambda 正在创建一个新函数。新的 lambda 函数不是槽,存在于主线程中。 lambda 函数接收者的线程是主线程。同时,worker.work
方法是一个具有不同接收线程的插槽。因此,信号知道在工作线程中调用 self.worker.work
,同时在主线程中调用 lambda 函数,然后在主线程中调用 self.worker.work()
。
我知道这很不方便,因为 lambda 对于将参数传递给函数很有用。
使用信号映射器传递值
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()
我有一个 Worker
对象并使用它的方法 moveToThread
将它放在一个线程中。
现在我调用它的work
方法:
- 如果我直接调用该方法,它会在其对象所在的线程中执行
- 如果我使用 lambda 调用该方法,该方法将在主线程中执行
示例:
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()
我做了过多的测试(参见片段中的代码),但我完全不知道发生了什么。
所以我的问题是:
为什么work
在主线程中执行,而不是在lambda
调用时在其对象所在的线程中执行?最重要的是,如果我想调用 Worker
的方法需要参数,而我无法节省 lambda
?
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_())
我想我有答案了。
我尝试了您的代码,并更改了 connect
方法以使用 QtCore.Qt.QueuedConnection
和 QtCore.Qt.DirectConnection
(connect(self.worker.work, Qt.QueuedConnection)
)。直接连接使两个函数以相同的方式(lambda 的工作方式)在主线程中都 运行 工作。但是,队列连接使它们的工作方式不同。 lambda 函数仍在主线程中 运行s,而 worker 函数在单独的线程中调用 运行s。注意:如果不在 connect
中给出参数,您将使用 AutoConnection,它将使用 QueuedConnection。
我通读了文档 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 运行ning 在主线程中,因为 lambda 正在创建一个新函数。新的 lambda 函数不是槽,存在于主线程中。 lambda 函数接收者的线程是主线程。同时,worker.work
方法是一个具有不同接收线程的插槽。因此,信号知道在工作线程中调用 self.worker.work
,同时在主线程中调用 lambda 函数,然后在主线程中调用 self.worker.work()
。
我知道这很不方便,因为 lambda 对于将参数传递给函数很有用。
使用信号映射器传递值
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()