From c4be200b2bf862817fd58c0c7f064121382365be Mon Sep 17 00:00:00 2001 From: Damien Jeandemange Date: Wed, 20 Nov 2024 23:34:31 +0100 Subject: [PATCH] Long-running tasks in background thread Signed-off-by: Damien Jeandemange --- yagat/app_context.py | 37 ++++++++++++++++++++++++++++++++++++- yagat/menus/impl/file.py | 26 +++++++++++++++++++++----- yagat/menus/impl/run.py | 26 ++++++++++++++++++-------- 3 files changed, 75 insertions(+), 14 deletions(-) diff --git a/yagat/app_context.py b/yagat/app_context.py index 49977a5..1761136 100644 --- a/yagat/app_context.py +++ b/yagat/app_context.py @@ -6,11 +6,12 @@ # SPDX-License-Identifier: MPL-2.0 # import logging +import threading import tkinter as tk from typing import Callable, Optional -import pypowsybl.network as pn import pypowsybl.loadflow as lf +import pypowsybl.network as pn import yagat.networkstructure as ns @@ -33,6 +34,8 @@ def __init__(self, root: tk.Tk): self.tab_group_changed_listeners: list[Callable[[str], None]] = [] self.tab_changed_listeners: list[Callable[[str], None]] = [] self.view_changed_listeners: list[Callable[[str], None]] = [] + self._long_running_task: Optional[threading.Thread] = None + self._network_changed_listener_enabled: bool = True @property def tk_root(self) -> tk.Tk: @@ -121,6 +124,8 @@ def add_network_changed_listener(self, listener: Callable[[Optional[pn.Network]] self.network_changed_listeners.append(listener) def notify_network_changed(self) -> None: + if not self.network_changed_listener_enabled: + return for listener in self.network_changed_listeners: listener(self.network) @@ -153,3 +158,33 @@ def add_view_changed_listener(self, listener: Callable[[str], None]) -> None: def notify_view_changed(self) -> None: for listener in self.view_changed_listeners: listener(self.selected_view) + + @property + def network_changed_listener_enabled(self) -> bool: + return self._network_changed_listener_enabled + + @network_changed_listener_enabled.setter + def network_changed_listener_enabled(self, value: bool) -> None: + self._network_changed_listener_enabled = value + + def start_long_running_task(self, name: str, target, args=(), on_done=None): + if self._long_running_task is not None: + self.status_text = 'Another task is already running, try again later' + return + self._long_running_task = threading.Thread(None, target, name, args) + logging.info(f'Task {self._long_running_task.name} starting') + self._long_running_task.start() + + def schedule_check(): + self._root.after(200, check_if_done) + + def check_if_done(): + if not self._long_running_task.is_alive(): + logging.info(f'Task {self._long_running_task.name} completed') + self._long_running_task = None + if on_done is not None: + on_done() + else: + schedule_check() + + schedule_check() diff --git a/yagat/menus/impl/file.py b/yagat/menus/impl/file.py index b86a01b..55ea8d5 100644 --- a/yagat/menus/impl/file.py +++ b/yagat/menus/impl/file.py @@ -89,8 +89,18 @@ def open_network(self): self.context.status_text = 'File opening cancelled by user' else: self.context.status_text = 'Opening ' + filename - self.context.network = pp.network.load(filename) - self.context.status_text = f'Network {self.context.network.name} loaded' + # disable the network changed listener, the tree view update is messed up in GUI if updated in thread + self.context.network_changed_listener_enabled = False + + def task(): + self.context.network = pp.network.load(filename) + + def on_done(): + self.context.status_text = f'Network {self.context.network.name} loaded' + self.context.network_changed_listener_enabled = True + self.context.notify_network_changed() + + self.context.start_long_running_task(name='Opening file', target=task, on_done=on_done) def save_network(self): if not self.context.network: @@ -99,6 +109,12 @@ def save_network(self): if not filename: self.context.status_text = 'File save cancelled by user' else: - self.context.status_text = 'Saving ' + filename - self.context.network.save(filename) - self.context.status_text = f'Network {self.context.network.name} saved to {filename}' + self.context.status_text = f'Saving {filename}' + + def on_done(): + self.context.status_text = f'Network {self.context.network.name} saved to {filename}' + + def task(): + self.context.network.save(filename) + + self.context.start_long_running_task(name='Saving file', target=task, on_done=on_done) diff --git a/yagat/menus/impl/run.py b/yagat/menus/impl/run.py index 7ebbd17..f3f39c0 100644 --- a/yagat/menus/impl/run.py +++ b/yagat/menus/impl/run.py @@ -20,14 +20,24 @@ def __init__(self, parent, context: AppContext, *args, **kwargs): self.parent = parent self.context = context parent.add_cascade(label="Run", menu=self) - self.add_command(label='Load Flow', command=self.run_load_flow) + self.add_command(label='AC Load Flow', command=lambda: self.run_load_flow(ac=True)) + self.add_command(label='DC Load Flow', command=lambda: self.run_load_flow(ac=False)) - def run_load_flow(self): + def run_load_flow(self, ac: bool): reporter = pr.Reporter() self.context.status_text = 'Starting Load Flow' - results = lf.run_ac(self.context.network, parameters=self.context.lf_parameters, reporter=reporter) - self.context.status_text = 'Load Flow completed' - self.context.network_structure.refresh() - self.context.notify_selection_changed() # hack to trigger refresh - print(results) - print(reporter) + + def on_done(): + self.context.network_structure.refresh() + self.context.notify_selection_changed() # hack to trigger refresh + self.context.status_text = 'Load Flow completed' + + def task(): + if ac: + results = lf.run_ac(self.context.network, parameters=self.context.lf_parameters, reporter=reporter) + else: + results = lf.run_dc(self.context.network, parameters=self.context.lf_parameters, reporter=reporter) + print(results) + print(reporter) + + self.context.start_long_running_task(name='Load Flow', target=task, on_done=on_done)