Skip to content

Commit

Permalink
Merge feature/multithreading (#58)
Browse files Browse the repository at this point in the history
🎉 Runs GUI and Kernel on separate threads
CAUTION application crashes if multiple executions are launched at the same time due to the lack of execution flow
  • Loading branch information
MathisFederico authored Nov 21, 2021
2 parents 62a23a0 + 4eded9e commit 843b732
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 19 deletions.
32 changes: 15 additions & 17 deletions opencodeblocks/graphics/blocks/codeblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

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

from ansi2html import Ansi2HTMLConverter

from opencodeblocks.graphics.blocks.block import OCBBlock
from opencodeblocks.graphics.pyeditor import PythonEditor
from opencodeblocks.graphics.worker import Worker

conv = Ansi2HTMLConverter()

Expand Down Expand Up @@ -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
5 changes: 3 additions & 2 deletions opencodeblocks/graphics/pyeditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -13,7 +13,7 @@
from opencodeblocks.graphics.kernel import Kernel

kernel = Kernel()

threadpool = QThreadPool()

if TYPE_CHECKING:
from opencodeblocks.graphics.view import OCBView
Expand All @@ -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()
Expand Down
47 changes: 47 additions & 0 deletions opencodeblocks/graphics/worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# OpenCodeBlock an open-source tool for modular visual programing in python
# Copyright (C) 2021 Mathïs FEDERICO <https://www.gnu.org/licenses/>

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

0 comments on commit 843b732

Please sign in to comment.