2

I have adapted the "async def _read_stream (stream, cb):" code described in How can I run an external command asynchronously from Python? by adding the update of a QTextEdit widget but the process is blocking when appending a text to the widget.

async def _read_stream(widget:RunCampaignWidget, stream, cb):
    while True:
        line = await stream.readline()
        if line:
            #cb(line)
            print("_read_stream :: line = " + str(line))
            widget.__run_result_console.append(line)
            widget.__run_result_console.show()
            global log_lines
            log_lines += str(line)
            #print("_read_stream :: log_lines = " + log_lines)
        else:
            break

In fact, I cannot update the GUI while running an asynchronous process. Any idea?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
pinchoonet
  • 21
  • 3

1 Answers1

3

PySide6 (and PyQt) not support asyncio by default but there are libraries like qasync that allow integrating eventloops. The following example is a simple sample of how to use asyncio.create_subprocess_exec() with PySide6

import sys
import asyncio

from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QApplication, QTextEdit, QPushButton, QVBoxLayout, QWidget

import qasync

if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())


class ManagerProcess(QObject):
    data_stdout_changed = Signal(bytes)
    data_stderr_changed = Signal(bytes)
    error_ocurred = Signal(Exception)

    started = Signal()
    finished = Signal()

    def start(self, cmd):
        asyncio.ensure_future(self._start(cmd))

    async def _start(self, cmd):
        try:
            process = await asyncio.create_subprocess_exec(
                *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
            )
            self.started.emit()
            await asyncio.wait(
                [
                    asyncio.create_task(
                        self._read_stream(process.stdout, self.data_stdout_changed)
                    ),
                    asyncio.create_task(
                        self._read_stream(process.stderr, self.data_stderr_changed)
                    ),
                ]
            )
            rc = await process.wait()
            self.finished.emit()
        except OSError as e:
            self.error_ocurred.emit(e)

    async def _read_stream(self, stream, signal):
        while True:
            line = await stream.readline()
            if line:
                signal.emit(line)
            else:
                break


class Widget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.text_edit = QTextEdit(readOnly=True)
        button = QPushButton("Start")

        lay = QVBoxLayout(self)
        lay.addWidget(button)
        lay.addWidget(self.text_edit)

        self.manager = ManagerProcess()

        button.clicked.connect(self.handle_clicked)
        self.manager.data_stdout_changed.connect(self.handle_stdout)
        self.manager.data_stderr_changed.connect(self.handle_stderr)

    def handle_clicked(self):
        cmd = ["ping", "8.8.8.8"]
        self.manager.start(cmd)

    def handle_stdout(self, data):
        self.text_edit.append(f"[STDOUT]: {data}")

    def handle_stderr(self, data):
        self.text_edit.append(f"[STDERR]: {data}")


def main():
    app = QApplication(sys.argv)
    loop = qasync.QEventLoop(app)
    asyncio.set_event_loop(loop)

    w = Widget()
    w.show()

    with loop:
        loop.run_forever()


if __name__ == "__main__":
    main()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thanks eyllanesc for your response, your code works after this fix: "self.error_ocurred.emit(e)", "e" argument was missing. – pinchoonet Jul 22 '21 at 09:36
  • But the QTextEdit is not updated in real time but after the process has finished. My process runs for a very long time (few minutes) and I need the QTextEdit to be updated and refreshed each time a line is read, not at the end of the process, it is too late at this time. – pinchoonet Jul 22 '21 at 13:12
  • @pinchoonet 1) I have already corrected the error, 2) I don't know what you mean, the GUI update is instantaneous, to verify now I use the "ping" command that returns a result every certain period of time and I observe that it is updated every instant. 3) Regarding the other post where there is an exception that you point out, clearly you are not using my code so I am not going to point out anything about it since I am only responsible for my code. – eyllanesc Jul 22 '21 at 14:50