Обновите графический интерфейс PySide из многопроцессорного процесса

Я пытаюсь создать графический интерфейс PySide, который обновляется многопроцессорным процессом, например графический интерфейс PySide, который отображает текст в окне, которое обновляется после некоторых вычислений. Используя QThread, я могу без проблем обновить графический интерфейс. Однако, если я попытаюсь сделать то же самое, используя многопроцессорный процесс вместо QThread (см. две строки кода непосредственно перед sys.exit), я получу ошибку. Вот минимальный пример:

import sys
from PySide import QtCore, QtGui
from multiprocessing import Process
import time

class GUI(QtGui.QMainWindow):

    def __init__(self):
        super(GUI, self).__init__()

        self.initUI()

    def initUI(self):

        self.text = "normal text"
        self.setGeometry(300, 300, 500, 300)
        self.setWindowTitle('TestGUI')
        self.show()

    def paintEvent(self, event):

        qp = QtGui.QPainter()
        qp.begin(self)
        self.drawText(event, qp)
        qp.end()

    def drawText(self, event, qp):

        qp.setPen(QtGui.QColor(0,0,0))
        qp.setFont(QtGui.QFont('Decorative', 50))
        qp.drawText(event.rect(), QtCore.Qt.AlignCenter, self.text)


    @QtCore.Slot(str)
    def setText(self, text):
        self.text = text
        print self.text
        self.repaint()


class Communicate(QtCore.QObject):
    updateGUI = QtCore.Signal(str)


class MyThread(QtCore.QThread):

    def __init__(self, com):
        super(MyThread, self).__init__()
        self.com = com

    def run(self):
        count = 0
        while True:
            self.com.updateGUI.emit("update %d" % count)
            count += 1
            time.sleep(1)


def loopEmit(com):
    while True:
        com.updateGUI.emit(time.ctime())
        time.sleep(1)


# Create and show GUI
app = QtGui.QApplication(sys.argv)
gui = GUI()
gui.show()

# connect signal and slot properly
com = Communicate()
com.updateGUI.connect(gui.setText)

thread = MyThread(com)
thread.start() # this works fine

time.sleep(0.5)

p = Process(target=loopEmit, args=[com])
p.start() # this breaks

sys.exit(app.exec_())

Проблема в том, что, по-видимому, GUI можно манипулировать только из основного процесса, поэтому попытка манипулировать им из нового процесса вызывает эту ошибку:

The process has forked and you cannot use this CoreFoundation functionality safely. You MUST exec().
Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_COREFOUNDATION_FUNCTIONALITY___YOU_MUST_EXEC__() to debug.

Мой немедленный ответ был - просто запустите вычисление в QThread. Но само вычисление довольно тяжелое, поэтому мне действительно нужно запустить его в отдельном процессе (и ядре). Спасибо!


person jjs    schedule 15.11.2014    source источник
comment
Разве это не то, что вы ищете? stackoverflow.com/questions/17272888/   -  person tiktok    schedule 15.11.2014
comment
да, похоже, спасибо! (Я некоторое время искал на SO, прежде чем публиковать это, но не нашел этого ответа). Однако я обнаружил, что apply_async не работает, если я даю ему метод класса, только если метод определен вне определения класса. Но это, вероятно, другая проблема, связанная с блокировкой/блокировкой. Так что да, спасибо, вы ответили на мой вопрос.   -  person jjs    schedule 18.11.2014
comment
еще один поиск предполагает, что методы экземпляра не могут быть обработаны и, следовательно, не могут быть переданы в apply_async (который использует травление для передачи объектов). Решение состоит в том, чтобы либо определить метод на верхнем уровне модуля (т.е. вне класса), как в stackoverflow.com/questions/8804830/ или добавить инфраструктуру, позволяющую травить эти методы, как описано здесь stackoverflow.com/questions/1816958/   -  person jjs    schedule 18.11.2014


Ответы (1)


Другим предложением может быть использование системы сигнализации Qt/PySide, чтобы полностью избежать любой блокировки.

И пример, используемый для разрешения трудоемкого процесса редактирования или получения данных из БАЗЫ ДАННЫХ, который вашему пользователю не нужно ждать, но вы хотите, чтобы пользовательский интерфейс обновлялся, как только он будет доступен.

Ниже приведен пример. Конечно, у нас нет ни тестового пользовательского интерфейса, ни тестовых данных для представления, но в примере показан класс потока QT, настроенный на отправку сигнала, когда данные готовы для отображения или использования в вызывающем приложении.

import pprint
try:
    from PySide import QtCore
except:
    from PySide2 import QtCore
from custom_module import DATABASE
from ui.data_widget import DataWidget

class BatchThread(QtCore.QThread):
    """
    Process the list of database batch queries as a threaded process and emit list when 
    complete.
    Or you could have the run process constantly emit signals if it is a looping process 
    you want to keep signaling it is processing.
    """
    sig = QtCore.Signal(list)
    def __init__(self, parent=None):
        QtCore.QThread.__init__(self, parent)
        self.data = {}

    def run(self):
        try:
            result = DATABASE.batchProcess(self.data)
            self.sig.emit(result)
        except:
            self.sig.emit([])


def executing_script(data=[]):
    """
    Main app that would have the UI elements you would want to update.
    """
    #   Assumption you have setup a textEdit widget called self.ui.displayWidget
    def __init__(self, given_args=[]):
        QtWidgets.QMainWindow.__init__(self, parent=None)
        self.ui = DataWidget()
        self.ui.setupUi(self)

        #   Create an instance of the independent process.
        self.async_process = BatchThread(self)
        #   Connect the sig signal to a function to execute when the result is emitted.
        self.async_process.sig.connect(self.display_result)

        #   Set the instance with data to process.
        self.async_process.data = ['<data to process you dont want to wait on>']
        #   Start it processing the data.
        self.async_process.run()
        #   Script execution continues.

    def display_result(self, result=[]):
        """
        When the process is finished, display it's result.
        Since your instance signal emits a list, that is what will be received along with the call to the function.
        """
        self.ui.displayWidget.clear()
        self.ui.displayWidget.append(str(pprint.pprint(result)))
person user13763876    schedule 17.06.2020