From 7ae5a18b03da450c7b1adfe5b295124c0a9c480e Mon Sep 17 00:00:00 2001 From: Alexandre Sajus Date: Sun, 21 Nov 2021 18:23:17 +0100 Subject: [PATCH] :tada: Runs GUI and Kernel on separate threads --- opencodeblocks/graphics/blocks/codeblock.py | 32 +++++++------- opencodeblocks/graphics/pyeditor.py | 5 ++- opencodeblocks/graphics/worker.py | 47 +++++++++++++++++++++ 3 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 opencodeblocks/graphics/worker.py diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py index cf9a3ab1..8db7f8dd 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/graphics/blocks/codeblock.py @@ -3,8 +3,7 @@ """ Module for the base OCB Code Block. """ - -from PyQt5.QtCore import QCoreApplication, QByteArray +from PyQt5.QtCore import QByteArray from PyQt5.QtGui import QPixmap from PyQt5.QtWidgets import QPushButton, QTextEdit @@ -12,6 +11,7 @@ from opencodeblocks.graphics.blocks.block import OCBBlock from opencodeblocks.graphics.pyeditor import PythonEditor +from opencodeblocks.graphics.worker import Worker conv = Ansi2HTMLConverter() @@ -144,19 +144,17 @@ def init_run_button(self): def run_code(self): """Run the code in the block""" code = self.source_editor.text() - kernel = self.source_editor.kernel self.source = code - # Execute the code - kernel.client.execute(code) - done = False - # While the kernel sends messages - while done is False: - # Keep the GUI alive - QCoreApplication.processEvents() - # Save kernel message and display it - output, output_type, done = kernel.update_output() - if done is False: - if output_type == 'text': - self.stdout = output - elif output_type == 'image': - self.image = output + # Create a worker to handle execution + worker = Worker(self.source_editor.kernel, self.source) + worker.signals.stdout.connect(self.handle_stdout) + worker.signals.image.connect(self.handle_image) + self.source_editor.threadpool.start(worker) + + def handle_stdout(self, stdout): + """ Handle the stdout signal """ + self.stdout = stdout + + def handle_image(self, image): + """ Handle the image signal """ + self.image = image diff --git a/opencodeblocks/graphics/pyeditor.py b/opencodeblocks/graphics/pyeditor.py index 70e571c6..2535e0ec 100644 --- a/opencodeblocks/graphics/pyeditor.py +++ b/opencodeblocks/graphics/pyeditor.py @@ -4,7 +4,7 @@ """ Module for OCB in block python editor. """ from typing import TYPE_CHECKING, List -from PyQt5.QtCore import Qt +from PyQt5.QtCore import QThreadPool, Qt from PyQt5.QtGui import QFocusEvent, QFont, QFontMetrics, QColor from PyQt5.Qsci import QsciScintilla, QsciLexerPython from opencodeblocks.graphics.theme_manager import theme_manager @@ -13,7 +13,7 @@ from opencodeblocks.graphics.kernel import Kernel kernel = Kernel() - +threadpool = QThreadPool() if TYPE_CHECKING: from opencodeblocks.graphics.view import OCBView @@ -33,6 +33,7 @@ def __init__(self, block: OCBBlock): super().__init__(None) self.block = block self.kernel = kernel + self.threadpool = threadpool self.setText(self.block.source) self.update_theme() diff --git a/opencodeblocks/graphics/worker.py b/opencodeblocks/graphics/worker.py new file mode 100644 index 00000000..331d6b15 --- /dev/null +++ b/opencodeblocks/graphics/worker.py @@ -0,0 +1,47 @@ +# OpenCodeBlock an open-source tool for modular visual programing in python +# Copyright (C) 2021 Mathïs FEDERICO + +""" Module to create and manage multi-threading workers """ + +import asyncio +from PyQt5.QtCore import QObject, pyqtSignal, QRunnable + + +class WorkerSignals(QObject): + """ Defines the signals available from a running worker thread. """ + stdout = pyqtSignal(str) + image = pyqtSignal(str) + + +class Worker(QRunnable): + """ Worker thread """ + + def __init__(self, kernel, code): + """ Initialize the worker object. """ + super(Worker, self).__init__() + + self.kernel = kernel + self.code = code + self.signals = WorkerSignals() + + async def run_code(self): + """ Run the code in the block """ + # Execute the code + self.kernel.client.execute(self.code) + done = False + # While the kernel sends messages + while done is False: + # Save kernel message and send it to the GUI + output, output_type, done = self.kernel.update_output() + if done is False: + if output_type == 'text': + self.signals.stdout.emit(output) + elif output_type == 'image': + self.signals.image.emit(output) + + def run(self): + """ Execute the run_code method asynchronously. """ + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(self.run_code()) + loop.close()