我正在尝试使用QThreads在Maya中更新自定义工具基于Qt的UI。我有一个线程可以执行任意方法,并通过发出的信号返回结果,然后我可以使用该信号来更新我的UI。这是我自定义的QThread类:
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)
我传递给线程的方法是从网址获取序列化数据的基本请求,例如:
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。有什么想法可以最好地避免在使用QThreads时使Maya崩溃,以及什么可能导致此特定问题?
答案 0 :(得分:2)
我们工作室的一位工程师发现了一些与使用Python线程和PyQt / PySide有关的错误。请参考:
记者的话:
尽管QObject是可重入的,但GUI类(尤其是QWidget及其所有子类)不是可重入的。只能在主线程中使用它们。
答案 1 :(得分:1)
这不能直接回答QThread
的问题,而是向您展示另一种在Maya中使用gui进行线程化的方法。
这是带有进度条和按钮的gui的简单示例。当用户单击按钮时,它将在不同的线程上创建一堆工作对象以执行time.sleep()
,并在完成时更新进度栏。由于它们位于不同的线程上,因此不会将用户锁定在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
:在工作进程内部使用,以便在完成后可以与池通信。 QObject
不会继承worker类,这意味着它本身不会发出信号,因此可以用来解决此问题。我知道这一切看起来很漫长,但是似乎可以在许多不同的工具中正常工作,而不会发生任何严重崩溃。