-
Notifications
You must be signed in to change notification settings - Fork 1
Split multithreaded executor #403
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
319cf1e
b1a89c7
7b91506
9013b14
7304e70
7208fa8
0b297e5
0764e0c
8886be6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,12 @@ | ||||||
| from dataclasses import dataclass | ||||||
| from typing import Callable | ||||||
| from typing import Iterable | ||||||
| from typing import List | ||||||
| from typing import Optional | ||||||
|
|
||||||
| from AnyQt.QtCore import QObject | ||||||
| from AnyQt.QtCore import QThread | ||||||
| from AnyQt.QtCore import pyqtSignal as Signal | ||||||
|
|
||||||
| from .base import TaskExecutor | ||||||
|
|
||||||
|
|
@@ -29,3 +35,139 @@ def cancel_running_task(self): | |||||
| """ | ||||||
| if self.current_task is not None: | ||||||
| self.current_task.cancel() | ||||||
|
|
||||||
|
|
||||||
| @dataclass | ||||||
| class _TaskExecutorState: | ||||||
| callbacks: Iterable[Callable[[ThreadedTaskExecutor], None]] | ||||||
| task_kwargs: Dict[str, Any] | ||||||
| log_missing_inputs: bool = False | ||||||
| task_executor: Optional[ThreadedTaskExecutor] = None | ||||||
|
|
||||||
|
|
||||||
| class MultiThreadedTaskExecutor(QObject): | ||||||
| """Create and execute each Ewoks task in its own dedicated thread.""" | ||||||
|
|
||||||
| sigComputationStarted = Signal() | ||||||
| """Signal emitted when a computation is started""" | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Docstring is not useful IMO. |
||||||
|
|
||||||
| sigComputationEnded = Signal() | ||||||
| """Signal emitted when a computation is ended""" | ||||||
|
|
||||||
| def __init__(self, ewokstaskclass): | ||||||
| super().__init__() | ||||||
| self.__ewokstaskclass = ewokstaskclass | ||||||
| self.__task_executors: List[_TaskExecutorState] = [] | ||||||
| self.__last_output_variables = dict() | ||||||
| self.__last_task_succeeded = None | ||||||
| self.__last_task_done = None | ||||||
| self.__last_task_exception = None | ||||||
|
|
||||||
| def execute_task( | ||||||
| self, | ||||||
| _callbacks: Iterable[Callable[[ThreadedTaskExecutor], None]] = tuple(), | ||||||
| log_missing_inputs: bool = False, | ||||||
| **kwargs, | ||||||
| ) -> Optional[ThreadedTaskExecutor]: | ||||||
| """Execute a prepared task, or directly create and execute a new one.""" | ||||||
| task_executor = ThreadedTaskExecutor(ewokstaskclass=self.__ewokstaskclass) | ||||||
| task_executor.create_task( | ||||||
| log_missing_inputs=log_missing_inputs, | ||||||
| **kwargs, | ||||||
| ) | ||||||
|
|
||||||
| state = _TaskExecutorState( | ||||||
| callbacks=tuple(_callbacks), | ||||||
| task_kwargs=kwargs, | ||||||
| log_missing_inputs=log_missing_inputs, | ||||||
| task_executor=task_executor, | ||||||
| ) | ||||||
| self.__add_task_executor(state) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think having a method |
||||||
|
|
||||||
| if task_executor.has_task: | ||||||
| task_executor.finished.connect(self.__process_ended) | ||||||
| self.sigComputationStarted.emit() | ||||||
| task_executor.start() | ||||||
| else: | ||||||
| self.__process_ended_direct(task_executor) | ||||||
|
|
||||||
| return task_executor | ||||||
|
|
||||||
| def __process_ended(self): | ||||||
| self.__process_ended_direct(self.sender()) | ||||||
|
|
||||||
| def _getState(self, task_executor: ThreadedTaskExecutor) -> _TaskExecutorState: | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other methods are in |
||||||
| return next( | ||||||
| ( | ||||||
| state | ||||||
| for state in self.__task_executors | ||||||
| if state.task_executor is task_executor | ||||||
| ), | ||||||
| None, | ||||||
| ) | ||||||
|
|
||||||
| def __process_ended_direct(self, task_executor: ThreadedTaskExecutor): | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| state = self._getState(task_executor=task_executor) | ||||||
| if state is None: | ||||||
| return | ||||||
|
|
||||||
| self.__last_output_variables = task_executor.output_variables | ||||||
| self.__last_task_succeeded = task_executor.succeeded | ||||||
| self.__last_task_done = task_executor.done | ||||||
| self.__last_task_exception = task_executor.exception | ||||||
|
|
||||||
| try: | ||||||
| for callback in state.callbacks: | ||||||
| callback(task_executor) | ||||||
| finally: | ||||||
| self.sigComputationEnded.emit() | ||||||
| self.__remove_task_executor(task_executor) | ||||||
|
|
||||||
| def stop(self, timeout: Optional[float] = None, wait: bool = False) -> None: | ||||||
| """Stop all tracked task threads.""" | ||||||
| for state in list(self.__task_executors): | ||||||
| task_executor = state.task_executor | ||||||
| if task_executor is None: | ||||||
| continue | ||||||
| if task_executor.receivers(task_executor.finished) > 0: | ||||||
| task_executor.finished.disconnect(self.__process_ended) | ||||||
| task_executor.stop(timeout=timeout, wait=wait) | ||||||
| self.__task_executors.clear() | ||||||
|
|
||||||
| def cancel_running_tasks(self, wait=True): | ||||||
| """Request cancellation of all running tasks.""" | ||||||
| for state in list(self.__task_executors): | ||||||
| if state.task_executor is None or not state.task_executor.isRunning(): | ||||||
| continue | ||||||
| task_executor = state.task_executor | ||||||
| task_executor.cancel_running_task() | ||||||
| task_executor.stop(wait=wait) | ||||||
|
|
||||||
| def __add_task_executor(self, state: _TaskExecutorState) -> None: | ||||||
| self.__task_executors.append(state) | ||||||
|
|
||||||
| def __remove_task_executor(self, task_executor: ThreadedTaskExecutor) -> None: | ||||||
| if task_executor is None: | ||||||
|
Comment on lines
+149
to
+150
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the type is right, this condition should never be true |
||||||
| return | ||||||
| for state in list(self.__task_executors): | ||||||
| if state.task_executor is task_executor: | ||||||
| if task_executor.receivers(task_executor.finished) > 0: | ||||||
| task_executor.finished.disconnect(self.__process_ended) | ||||||
| self.__task_executors.remove(state) | ||||||
| break | ||||||
|
|
||||||
| @property | ||||||
| def task_succeeded(self) -> Optional[bool]: | ||||||
| return self.__last_task_succeeded | ||||||
|
|
||||||
| @property | ||||||
| def task_done(self) -> Optional[bool]: | ||||||
| return self.__last_task_done | ||||||
|
|
||||||
| @property | ||||||
| def task_exception(self) -> Optional[Exception]: | ||||||
| return self.__last_task_exception | ||||||
|
|
||||||
| @property | ||||||
| def output_variables(self) -> dict: | ||||||
| return self.__last_output_variables | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need. This module is deprecated like many thinking under
ewoksorange.bindings