2

Well, for this minimal example we have one main window and one qdialog window. Every of these has one button.

When the user clicks in the button of main window the qdialog window is open.

When the user clicks in the button of qdialog, I want to run in multiprocessing (asynchronous) a function from mainwindow. But instead of these it opens again a new mainwindow.

Code:

untitled.py

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'untitled.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(318, 41)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.open_dialog = QtWidgets.QPushButton(self.centralwidget)
        self.open_dialog.setObjectName("open_dialog")
        self.gridLayout.addWidget(self.open_dialog, 0, 0, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.open_dialog.setText(_translate("MainWindow", "Open Dialog"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

dialog.py

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'dialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(400, 41)
        self.gridLayout = QtWidgets.QGridLayout(Dialog)
        self.gridLayout.setObjectName("gridLayout")
        self.call_method_button = QtWidgets.QPushButton(Dialog)
        self.call_method_button.setObjectName("call_method_button")
        self.gridLayout.addWidget(self.call_method_button, 0, 0, 1, 1)

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
        self.call_method_button.setText(_translate("Dialog", "Call method from main Window"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Dialog = QtWidgets.QDialog()
    ui = Ui_Dialog()
    ui.setupUi(Dialog)
    Dialog.show()
    sys.exit(app.exec_())

main.py

from PyQt5 import QtCore, QtGui, QtWidgets
import sys
from untitled import *
from dialog import Ui_Dialog
from dialog_code import Dialog_Code

class MainCode:
    def __init__(self):
        self.app = QtWidgets.QApplication(sys.argv)
        self.mainWindow = QtWidgets.QMainWindow()
        self.main_ui = Ui_MainWindow()
        self.main_ui.setupUi(self.mainWindow)
        self.mainWindow.show()
        
        self.main_ui.open_dialog.clicked.connect(self.open_dialog_window)
        
        sys.exit(self.app.exec_())
        
    def open_dialog_window(self):
        self.dialog_window = QtWidgets.QDialog(self.mainWindow)
        self.ui_dialog_window = Ui_Dialog()
        self.ui_dialog_window.setupUi(self.dialog_window)
        self.dialog_window.show()
        dialog_window_run_code = Dialog_Code(self)
        
    def print_ok_123(self):
        print("print_ok_123 method")
        for i in range(0,10):
            print("Ok")
            
        return 1
        
        
program = MainCode()

dialog_code.py

from multiprocessing import Pool
class Dialog_Code:
    def __init__(self,main_self):
        print("Dialog init")
        self.main_self = main_self
        
        self.main_self.ui_dialog_window.call_method_button.clicked.connect(lambda:self.call_main_method())
        
    def call_main_method(self):
        print("Button pressed")
        self.pool = Pool(processes=1)
        result = self.pool.apply_async(self.main_self.print_ok_123, tuple(), self.print_ok_end_123)
        self.pool.close()
        self.pool.join()
        
    def print_ok_end_123(self):
        print("End of main method")

To run the code run python main.py

What's wrong with the pool statement, and how can i fix it?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Chris P
  • 2,059
  • 4
  • 34
  • 68
  • I cannot reproduce the issue of the new main window getting shown again. Also, it doesn't seem that you're correctly using the pool, as the function doesn't get executed at all (I never used multiprocessing, but from the look of `apply_async` docs you're not using arguments correctly). – musicamante May 01 '21 at 18:02

1 Answers1

1

If you are invoking a worker function that takes no additional arguments other than self and are willing to block until the result is returned, then it is simpler to use method apply:

result = self.pool.apply(self.main_self.print_ok_123)

This is equivalent to using the non-blocking apply_async this way:

async_result = self.pool.apply_async(self.main_self.print_ok_123)
# block until the result is ready and get the result:
result = async_result.get()

The key point is that the call self.pool.apply_async(self.main_self.print_ok_123) does not produce the return value from calling self.main_self.print_ok_123 but rather an AsyncResult instance whose get method you have to call to get the actual return value. So while the calls to self.pool.close() and self.pool.join() will block until all outstanding tasks have completed, there would be no way of getting the actual return value from the call to self.main_self.print_ok_123 without calling get on that AsyncResult object.

Also, If you are now using method apply, which by definition blocks until the task completes, strictly speaking you do not have to call close and join. If you want to force clean up of your pool now, you could call self.pool.terminate() or you could just wait for the pool to be garbage collected when there are no longer any references to it.

Update

import multiprocessing as mp

class Foo():
    @staticmethod
    def work1():
        return 1

    def work2(self):
        return 2

if __name__ == '__main__':
    pool = mp.Pool(1)
    foo = Foo()
    print(pool.apply(foo.work1))
    print(pool.apply(foo.work2))

Prints

1
2
Booboo
  • 38,656
  • 3
  • 37
  • 60
  • thanks for the reply. I have openned a new similar question here: https://stackoverflow.com/questions/67357180/python-pyqt5-freezing-issue – Chris P May 02 '21 at 13:54
  • @ChrisP Without my delving deep into your code, I question what is gained by creating a pool of size 1 and then having the thread block until the submitted task completes. First, unless the return value is of great importance, it is cheaper just to create a single `Process`. But moreover, why not just call the function directly? Why does it need to execute in a new process? – Booboo May 02 '21 at 13:58
  • you can see the full problem here: https://www.youtube.com/watch?v=9emMPLo46KY , after that i put the pyaudio stream in a worker thread and now only the workers emit signals are freezing (timeline,time,volume strength - all display) when the user clicks one of the two bottom buttons of video. – Chris P May 02 '21 at 14:08
  • I have a TypeError when i am calling get() --> `TypeError: cannot pickle 'QApplication' object` What's wrong about? – Chris P May 02 '21 at 21:01
  • If `self.main_self.print_ok_123` refers to method `print_ok_123` in class `MainCode`, then I doubt it is actually returning 1, right? Any arguments you pass to `apply` and any return values you return back must be serializable using module `pickle` because these objects are moving from one address space to another. Perhaps you can get away with using multithreading instead of multiprocessing. – Booboo May 02 '21 at 22:00
  • I am on the bed now but I have found this: https://stackoverflow.com/questions/8804830/python-multiprocessing-picklingerror-cant-pickle-type-function . I will try it tomorrow. Good night. – Chris P May 02 '21 at 22:25
  • 1
    First, that link is 9 years old. Second, the code posted with the very highly updated answer is wrong: a method that is decorated with `@staticmethod` does not take a `self` argument. See my update with corrected code, both with a static method and a regular method and there is no issues. – Booboo May 02 '21 at 22:49