Mercurial > libervia-backend
diff libervia/frontends/tools/aio.py @ 4138:5de6f3595380
frontends (tools/aio): add tool to run from thread, run maybe async method, or run GLib loop:
- `run_from_thread` can be used to run a method from a thread.
- `maybe_async` can be used to run a method if we are not sure if it's async or not.
- `install_glib_asyncio_iteration` run the GLib loop from asyncio one's.
rel 426
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 01 Nov 2023 14:00:48 +0100 |
parents | 5fc26a6ef113 |
children | d01b8d002619 |
line wrap: on
line diff
--- a/libervia/frontends/tools/aio.py Wed Nov 01 13:57:08 2023 +0100 +++ b/libervia/frontends/tools/aio.py Wed Nov 01 14:00:48 2023 +0100 @@ -17,7 +17,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import asyncio -from typing import Any, Coroutine +from typing import Any, Awaitable, Callable, Coroutine from libervia.backend.core import log as logging @@ -54,3 +54,95 @@ background_tasks.add(task) task.add_done_callback(_on_task_done) + + +def run_with_args( + async_method: Callable[..., Coroutine[Any, Any, Any]], *args: Any, **kwargs: Any +) -> None: + """Schedules and tracks an asynchronous method with arguments. + + This function wraps the provided asynchronous method with its arguments + and then schedules it for execution. + + @param async_method: The asynchronous method to be scheduled. + @param args: Positional arguments to pass to the async_method. + @param kwargs: Keyword arguments to pass to the async_method. + """ + run_async(async_method(*args, **kwargs)) + + +def run_from_thread( + async_method: Coroutine | asyncio.Future, + *args, + loop: asyncio.AbstractEventLoop | None = None, + **kwargs, +) -> None: + """Schedules an asynchronous method from another thread. + + @param async_method: The method to be scheduled for execution. + """ + if loop is None: + loop = asyncio.get_event_loop() + assert loop is not None + loop.call_soon_threadsafe(run_with_args, async_method, *args, **kwargs) + + +def maybe_async(result: Any | Awaitable[Any]) -> Awaitable[Any]: + """ + Convert the provided result into an awaitable. + + @param result: the result of a function or coroutine call + @return: an awaitable object which can be awaited to get the result + """ + if asyncio.iscoroutine(result): + return result + + future = asyncio.Future() + future.set_result(result) + return future + + +def install_glib_asyncio_iteration(): + """Import and install GLib context iteration inside our asyncio event loop. + + This is used as soon as GLib is used (like GStreamer). + Inspired from Kivy's install_gobject_iteration (in ``kivy.support``), thanks to Kivy's + team. + """ + + import asyncio + + try: + from gi.repository import GLib + except ImportError: + raise ImportError("GLib could not be imported. Ensure it's installed.") + + if hasattr(GLib, "_glib_already_installed"): + # already installed, don't do it twice. + return + + GLib._glib_already_installed = True + + loop = asyncio.get_event_loop() + + # Create a GLib MainContext and make it the default + glib_context = GLib.MainContext.default() + + # Function to iterate over the GLib main context + def _glib_iteration(): + # We need to loop over the context to prevent lag + iteration_count = 0 + while glib_context.pending() and iteration_count < 20: + glib_context.iteration(False) + iteration_count += 1 + + # If no work was done in the GLib loop, add a short delay before + # scheduling the next iteration, to prevent spinning and high CPU usage. + if iteration_count == 0: + loop.call_later(0.01, _glib_iteration) + else: + # Schedule ourselves to run again on the next asyncio loop iteration + loop.call_soon(_glib_iteration) + + # Kick off the GLib iteration + loop.call_soon(_glib_iteration)