1

I'm trying to create a PySide GUI that gets updated by a multiprocessing Process, for example a PySide GUI that displays text in a window that gets updated after some computation. By using a QThread, I am able to update the GUI without any problems. However, if I try to do the same using a multiprocessing Process instead of a QThread (cf. the two lines of code just before sys.exit), I get an error. Here's a minimal example:

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_())

The problem is that apparently the GUI can only be manipulated from the main process, so trying to manipulate it from a new process raises this error:

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.

My immediate response was- just run the computation in a QThread. But the computation itself is pretty heavy and so I really need to run it in a separate process (and core) altogether. Thanks!

jjs
  • 594
  • 8
  • 13
  • 1
    Isn't this what you're looking for? http://stackoverflow.com/questions/17272888/how-to-use-multiprocessing-pool-correctly-with-pyside-to-create-a-non-blocking-g?rq=1 – tiktok Nov 15 '14 at 01:37
  • yes, it looks like it is, thanks! (I searched on SO for a while before posting this but didn't find that answer). I am however finding that apply_async doesn't work if I give it a class method, only if the method is defined outside a class definition. But that's probably a different issue to do with locking/blocking. So yes, thanks, you've answered my question. – jjs Nov 17 '14 at 21:46
  • some more searching suggests instance methods are not "pickleable" and hence can't be passed to apply_async (which uses pickling to pass objects around). The solution is either to define the method at the top level of the module (i.e. outside the class) as in http://stackoverflow.com/questions/8804830/python-multiprocessing-pickling-error or to "add the infrastructure" to allow these methods to be pickled, as described here http://stackoverflow.com/questions/1816958/cant-pickle-type-instancemethod-when-using-pythons-multiprocessing-pool-ma – jjs Nov 17 '14 at 22:28

1 Answers1

1

Another suggestion might be to use the Qt/PySide signaling system to completely avoid any sort of blocking.

And example used for letting a time consuming process for editing or acquiring data from a DATABASE that your user doesn't need to wait for, but you want the UI to update once it is available.

Below is an example. Granted don't have a test ui nor test data to present, but the example shows a QT thread class that is set to emit a signal when data is ready to display or use in the calling application.

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)))