在Maya中正确使用PySide QThread以避免硬崩溃

时间:2019-06-17 10:36:13

标签: 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):
        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崩溃,以及什么可能导致此特定问题?

2 个答案:

答案 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个不同的类。

  1. ThreadPool:这负责创建和管理所有辅助对象。此类还负责通过信号与gui通信,以便在工作人员完成工作时做出相应的反应。
  2. Worker:这是实际的繁重任务,您想要在线程中进行任何处理。
  3. ThreadSignals:在工作进程内部使用,以便在完成后可以与池通信。 QObject不会继承worker类,这意味着它本身不会发出信号,因此可以用来解决此问题。

我知道这一切看起来很漫长,但是似乎可以在许多不同的工具中正常工作,而不会发生任何严重崩溃。