2020-01-19 01:05:37 +01:00
|
|
|
# Copyright: Ankitects Pty Ltd and contributors
|
|
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
|
|
|
|
"""
|
|
|
|
Helper for running tasks on background threads.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from concurrent.futures import Future
|
|
|
|
from concurrent.futures.thread import ThreadPoolExecutor
|
|
|
|
from threading import Lock
|
|
|
|
from typing import Any, Callable, Dict, List, Optional
|
|
|
|
|
|
|
|
from PyQt5.QtCore import QObject, pyqtSignal
|
|
|
|
|
2020-01-22 05:09:51 +01:00
|
|
|
Closure = Callable[[], None]
|
2020-01-19 01:05:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
class TaskManager(QObject):
|
2020-01-22 05:09:51 +01:00
|
|
|
_closures_pending = pyqtSignal()
|
2020-01-19 01:05:37 +01:00
|
|
|
|
2020-02-27 04:27:58 +01:00
|
|
|
def __init__(self) -> None:
|
2020-01-19 01:05:37 +01:00
|
|
|
QObject.__init__(self)
|
|
|
|
self._executor = ThreadPoolExecutor()
|
2020-01-22 05:09:51 +01:00
|
|
|
self._closures: List[Closure] = []
|
|
|
|
self._closures_lock = Lock()
|
|
|
|
self._closures_pending.connect(self._on_closures_pending) # type: ignore
|
2020-01-19 01:05:37 +01:00
|
|
|
|
2020-01-22 05:09:51 +01:00
|
|
|
def run_on_main(self, closure: Closure):
|
|
|
|
"Run the provided closure on the main thread."
|
|
|
|
with self._closures_lock:
|
|
|
|
self._closures.append(closure)
|
|
|
|
self._closures_pending.emit() # type: ignore
|
2020-01-19 01:05:37 +01:00
|
|
|
|
2020-01-22 05:09:51 +01:00
|
|
|
def run_in_background(
|
2020-01-19 01:05:37 +01:00
|
|
|
self,
|
|
|
|
task: Callable,
|
2020-01-22 05:09:51 +01:00
|
|
|
on_done: Optional[Callable[[Future], None]] = None,
|
2020-01-19 01:05:37 +01:00
|
|
|
args: Optional[Dict[str, Any]] = None,
|
|
|
|
) -> Future:
|
2020-01-22 05:09:51 +01:00
|
|
|
"""Run task on a background thread.
|
|
|
|
|
|
|
|
If on_done is provided, it will be called on the main thread with
|
|
|
|
the completed future.
|
2020-01-19 01:05:37 +01:00
|
|
|
|
|
|
|
Args if provided will be passed on as keyword arguments to the task callable."""
|
|
|
|
if args is None:
|
|
|
|
args = {}
|
|
|
|
|
|
|
|
fut = self._executor.submit(task, **args)
|
|
|
|
|
|
|
|
if on_done is not None:
|
2020-01-22 05:09:51 +01:00
|
|
|
fut.add_done_callback(
|
|
|
|
lambda future: self.run_on_main(lambda: on_done(future))
|
2020-01-19 01:05:37 +01:00
|
|
|
)
|
|
|
|
|
2020-01-22 05:09:51 +01:00
|
|
|
return fut
|
2020-01-19 01:05:37 +01:00
|
|
|
|
2020-01-22 05:09:51 +01:00
|
|
|
def _on_closures_pending(self):
|
|
|
|
"""Run any pending closures. This runs in the main thread."""
|
|
|
|
with self._closures_lock:
|
|
|
|
closures = self._closures
|
|
|
|
self._closures = []
|
2020-01-19 01:05:37 +01:00
|
|
|
|
2020-01-22 05:09:51 +01:00
|
|
|
for closure in closures:
|
|
|
|
closure()
|