anki/qt/aqt/taskman.py

89 lines
2.6 KiB
Python
Raw Normal View History

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 __future__ import annotations
2020-01-19 01:05:37 +01:00
from concurrent.futures import Future
from concurrent.futures.thread import ThreadPoolExecutor
from threading import Lock
2020-05-04 13:52:56 +02:00
from typing import Any, Callable, Dict, List, Optional
2020-01-19 01:05:37 +01:00
from PyQt5.QtCore import QObject, pyqtSignal
import aqt
from aqt.qt import QWidget, qconnect
Closure = Callable[[], None]
2020-01-19 01:05:37 +01:00
class TaskManager(QObject):
_closures_pending = pyqtSignal()
2020-01-19 01:05:37 +01:00
def __init__(self, mw: aqt.AnkiQt) -> None:
2020-01-19 01:05:37 +01:00
QObject.__init__(self)
self.mw = mw.weakref()
2020-01-19 01:05:37 +01:00
self._executor = ThreadPoolExecutor()
self._closures: List[Closure] = []
self._closures_lock = Lock()
qconnect(self._closures_pending, self._on_closures_pending)
2020-01-19 01:05:37 +01:00
2021-02-01 14:28:21 +01:00
def run_on_main(self, closure: Closure) -> None:
"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
def run_in_background(
2020-01-19 01:05:37 +01:00
self,
task: Callable,
on_done: Optional[Callable[[Future], None]] = None,
2020-01-19 01:05:37 +01:00
args: Optional[Dict[str, Any]] = None,
) -> Future:
"""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:
fut.add_done_callback(
lambda future: self.run_on_main(lambda: on_done(future))
2020-01-19 01:05:37 +01:00
)
return fut
2020-01-19 01:05:37 +01:00
def with_progress(
self,
task: Callable,
on_done: Optional[Callable[[Future], None]] = None,
parent: Optional[QWidget] = None,
2020-05-30 04:28:22 +02:00
label: Optional[str] = None,
2020-05-31 03:49:05 +02:00
immediate: bool = False,
2021-02-02 14:30:53 +01:00
) -> None:
self.mw.progress.start(parent=parent, label=label, immediate=immediate)
2021-02-02 14:30:53 +01:00
def wrapped_done(fut: Future) -> None:
self.mw.progress.finish()
if on_done:
on_done(fut)
self.run_in_background(task, wrapped_done)
def _on_closures_pending(self) -> None:
"""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
for closure in closures:
closure()