pythonpyside6

Send messages to Qt GUI from a process pool. PySide6


I have an app that downloads files from an ftp server and it does so in a process pool to save time because there can be a lot of files at times.

I'd like to show the progress of what files are being downloaded by displaying a message in a QLabel on the GUI.

First module :

import os
import multiprocessing

shared_queue = multiprocessing.Queue()

class MyProcess:
    def foo(self, nb):
        pid = os.getpid()
        output = f"Process id = {pid}\nReceived nb = {nb}" 
        shared_queue.put(output)
        print(output)

    def do_stuff(self):
        pool_size = 8
        p = multiprocessing.Pool(pool_size)
        p.map(self.foo, range(pool_size))

I replaced the ftp download function by a smaller print one but I get the exact same problem with both.

main module:

import sys
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout
from PySide6.QtCore import QThread, Signal

from other_module import MyProcess, shared_queue 

class MyThread(QThread):
    my_sig = Signal(str)

    def run(self):
        while True:
            text = shared_queue.get(block=True)
            self.my_sig.emit(text)

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.label = QLabel("label to update")
        self.button = QPushButton("Do Stuff")

        self.layout = QVBoxLayout()
        self.layout.addWidget(self.label)
        self.layout.addWidget(self.button)
        self.setLayout(self.layout)

        self.thread = MyThread()
        self.thread.my_sig.connect(self.update_label)
        self.thread.start()

        self.button.clicked.connect(self.button_handler)

    def update_label(self, text):
        self.label.setText(text)
        print(text)
        self.label.update()
        QApplication.processEvents()

    def button_handler(self):
        bar = MyProcess()
        bar.do_stuff()

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    sys.exit(app.exec())

When I do that, the label only gets updated after do_stuff() finishes. The messages are put in the queue but they're only fetched in one go once the function ends. But what I'd want is showing the message on the GUI as soon as it's emitted. Does anyone know how I could do that ?


Solution

  • After re-reading the doc, I tried with imap_unordered() and it worked. So only the code of do_stuff() needed to be altered and it now looks like this :

    def do_stuff():
        pool_size = 8
        p = multiprocessing.Pool(pool_size)
        p.imap_unordered(self.foo, range(pool_size))