Python 在Maya中正确使用PySide QThread以避免硬崩溃
我正在尝试使用QThreads在Maya中更新自定义工具的基于Qt的UI。我有一个线程,它执行任意方法,并通过发出的信号返回结果,然后我用它来更新我的UI。下面是我的自定义QThread类:Python 在Maya中正确使用PySide QThread以避免硬崩溃,python,pyside,maya,qthread,Python,Pyside,Maya,Qthread,我正在尝试使用QThreads在Maya中更新自定义工具的基于Qt的UI。我有一个线程,它执行任意方法,并通过发出的信号返回结果,然后我用它来更新我的UI。下面是我的自定义QThread类: from PySide import QtCore class Thread(QtCore.QThread): result = QtCore.Signal(object) def __init__(self, parent, method, **kwargs): s
from PySide import QtCore
class Thread(QtCore.QThread):
result = QtCore.Signal(object)
def __init__(self, parent, method, **kwargs):
super(Thread, self).__init__(parent)
self.parent = parent
self.method = method
self.kwargs = kwargs
def run(self):
result = self.method(**self.kwargs)
self.result.emit(result)
我传递给线程的方法是从web地址获取序列化数据的基本请求,例如:
import requests
def request_method(address):
request = requests.get(address)
return request.json()
下面是我如何使用自定义工具中的线程来动态更新我的UI:
...
thread = Thread(parent=self, method=request_method, address='http://www.example.com/')
thread.result.connect(self._slot_result)
thread.start()
def _slot_result(self, result):
# Use the resulting data to update some UI element:
self.label.setText(result)
...
此工作流适用于其他DCC(如Nuke),但由于某些原因,它有时会导致Maya不一致地崩溃。没有错误消息,没有日志,只是硬崩溃
这让我觉得我的QThread工作流设计显然不利于Maya。您知道在使用QThread时如何最好地避免Maya崩溃,以及是什么导致了此特定问题吗?这并不是直接回答您的
QThread
的问题,而是向您展示在Maya中使用GUI进行线程处理的另一种方法
下面是一个简单的gui示例,它有一个进度条和一个按钮。当用户单击按钮时,它将在不同的线程上创建一组worker对象来执行time.sleep()
,并在完成时更新进度条。由于它们位于不同的线程上,因此不会从gui锁定用户,因此用户仍可在gui更新时与其交互:
from functools import partial
import traceback
import time
from PySide2 import QtCore
from PySide2 import QtWidgets
class Window(QtWidgets.QWidget):
"""
Your main gui class that contains a progress bar and a button.
"""
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# Create our main thread pool object that will handle all the workers and communication back to this gui.
self.thread_pool = ThreadPool(max_thread_count=5) # Change this number to have more workers running at the same time. May need error checking to make sure enough threads are available though!
self.thread_pool.pool_started.connect(self.thread_pool_on_start)
self.thread_pool.pool_finished.connect(self.thread_pool_on_finish)
self.thread_pool.worker_finished.connect(self.worker_on_finish)
self.progress_bar = QtWidgets.QProgressBar()
self.button = QtWidgets.QPushButton("Run it")
self.button.clicked.connect(partial(self.thread_pool.start, 30)) # This is the number of iterations we want to process.
self.main_layout = QtWidgets.QVBoxLayout()
self.main_layout.addWidget(self.progress_bar)
self.main_layout.addWidget(self.button)
self.setLayout(self.main_layout)
self.setWindowTitle("Thread example")
self.resize(500, 0)
def thread_pool_on_start(self, count):
# Triggers right before workers are about to be created. Start preparing the gui to be in a "processing" state.
self.progress_bar.setValue(0)
self.progress_bar.setMaximum(count)
def thread_pool_on_finish(self):
# Triggers when all workers are done. At this point you can do a clean-up on your gui to restore it to it's normal idle state.
if self.thread_pool._has_errors:
print "Pool finished with no errors!"
else:
print "Pool finished successfully!"
def worker_on_finish(self, status):
# Triggers when a worker is finished, where we can update the progress bar.
self.progress_bar.setValue(self.progress_bar.value() + 1)
class ThreadSignals(QtCore.QObject):
"""
Signals must inherit from QObject, so this is a workaround to signal from a QRunnable object.
We will use signals to communicate from the Worker class back to the ThreadPool.
"""
finished = QtCore.Signal(int)
class Worker(QtCore.QRunnable):
"""
Executes code in a seperate thread.
Communicates with the ThreadPool it spawned from via signals.
"""
StatusOk = 0
StatusError = 1
def __init__(self):
super(Worker, self).__init__()
self.signals = ThreadSignals()
def run(self):
status = Worker.StatusOk
try:
time.sleep(1) # Process something big here.
except Exception as e:
print traceback.format_exc()
status = Worker.StatusError
self.signals.finished.emit(status)
class ThreadPool(QtCore.QObject):
"""
Manages all Worker objects.
This will receive signals from workers then communicate back to the main gui.
"""
pool_started = QtCore.Signal(int)
pool_finished = QtCore.Signal()
worker_finished = QtCore.Signal(int)
def __init__(self, max_thread_count=1):
QtCore.QObject.__init__(self)
self._count = 0
self._processed = 0
self._has_errors = False
self.pool = QtCore.QThreadPool()
self.pool.setMaxThreadCount(max_thread_count)
def worker_on_finished(self, status):
self._processed += 1
# If a worker fails, indicate that an error happened.
if status == Worker.StatusError:
self._has_errors = True
if self._processed == self._count:
# Signal to gui that all workers are done.
self.pool_finished.emit()
def start(self, count):
# Reset values.
self._count = count
self._processed = 0
self._has_errors = False
# Signal to gui that workers are about to begin. You can prepare your gui at this point.
self.pool_started.emit(count)
# Create workers and connect signals to gui so we can update it as they finish.
for i in range(count):
worker = Worker()
worker.signals.finished.connect(self.worker_finished)
worker.signals.finished.connect(self.worker_on_finished)
self.pool.start(worker)
def launch():
global inst
inst = Window()
inst.show()
除了主gui之外,还有3个不同的类
ThreadPool
:负责创建和管理所有辅助对象。这个类还负责用信号与gui通信,以便在工作人员完成任务时做出相应的反应Worker
:这就是实际的繁重工作以及您希望在线程中处理的任何内容ThreadSignals
:这在工作线程内部使用,以便在完成后能够与池通信。worker类不是由QObject
继承的,这意味着它本身不能发出信号,因此这是一种解决方法我知道这一切看起来都很冗长,但它似乎在一系列不同的工具中运行良好,没有任何硬崩溃。我们工作室的一位工程师发现了一些与Python线程和PyQt/PySide的使用有关的bug。请参阅:
- [PySide 1.x]
- [PySide 2.x]
为了进一步澄清崩溃情况,如果我运行工具一次,它不会崩溃Maya,但如果我关闭它并再次运行相同的代码,Maya会在执行问题中概述的线程方法时崩溃…Maya说使用线程是不安全的,我从不在线程中冒险,所以我不知道是否有解决方法:@DrWeeny谢谢您的评论。该链接引用运行maya.cmds命令的常规Python线程。我知道这是非常不安全的-在我目前的情况下,我没有调用任何maya.cmds方法,而是通过QThreads发出GET请求从服务器获取数据,这应该是安全的,但显然不是…感谢您提供了这个替代方案,我以前没有使用过此工作流。我将在我的工具中测试它,看看它是否表现得更好。我测试了您提出的替代方案,虽然它确实似乎使线程更稳定,但Maya仍然无法预测地崩溃,因此我无法将此标记为答案。谢谢你的分享,我会在其他脚本中使用这个工作流。你认为你可以用一个最小的仍然崩溃的完整示例来编辑你的文章吗?如果我也能确认崩溃,那就太好了。除了我最初的问题,我真的无法得到任何更具体的答案,基本上我有一个方法来发出get请求,并从服务器获取JSON响应数据,我想线程化。响应数据是一个简单的键值字典,我用它来填充QTableWidget行和一些QComboBox元素,仅此而已。令人沮丧的是,我无法确定导致崩溃的任何特定请求,每个会话的请求都不同。其他人认为可能是错误/异常导致了崩溃,因为假定QThreads/QRunnables不能很好地处理这些请求,但我所有的请求和UI操作在主线程上都可以正常工作,所以我真的不确定这个问题是从哪里来的。冷静点!