1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
|
.. currentmodule:: gi.repository
Asynchronous Programming
========================
Asynchronous programming is an essential paradigm for handling tasks like I/O
operations, ensuring that your application remains responsive. GLib supports
asynchronous operations alongside its synchronous counterparts. These
asynchronous functions typically have a ``_async`` suffix and execute tasks in
the background, invoking a callback or returning a future-like object upon
completion or cancellation.
PyGObject offers two primary ways to implement asynchronous programming:
1. **Using Python's `asyncio` module** (available since PyGObject 3.50)
2. **Using callbacks** (the traditional approach)
Asynchronous Programming with ``asyncio``
-----------------------------------------
.. attention::
Asyncio support for PyGObject is **experimental**. Feel free
to explore its integration, but note that the API is subject to change as
potential issues are addressed.
Overview of `asyncio` Integration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PyGObject integrates seamlessly with Python's :mod:`asyncio` module by
providing:
1. An :mod:`asyncio` event loop implementation, enabling normal operation of
Python's asynchronous code.
2. Awaitable objects for Gio's asynchronous functions, allowing ``await``
syntax within Python coroutines.
Event Loop Integration
~~~~~~~~~~~~~~~~~~~~~~
To use the :mod:`asyncio` event loop with GLib, set up the GLib-based event loop
policy:
.. code-block:: python
import asyncio
from gi.events import GLibEventLoopPolicy
# Set up the GLib event loop
policy = GLibEventLoopPolicy()
asyncio.set_event_loop_policy(policy)
Now, fetch the event loop and submit tasks:
.. code-block:: python
loop = policy.get_event_loop()
async def do_some_work():
await asyncio.sleep(2)
print("Done working!")
task = loop.create_task(do_some_work())
.. note::
Keep a reference to the tasks you create, as :mod:`asyncio` only
maintains weak references to them.
Gio Asynchronous Function Integration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If the callback parameter of a Gio asynchronous function is omitted, PyGObject
automatically returns an awaitable object (similar to :class:`asyncio.Future`).
This allows you to use ``await`` and cancel operations from within a coroutine.
.. code-block:: python
loop = policy.get_event_loop()
async def list_files():
f = Gio.file_new_for_path("/")
for info in await f.enumerate_children_async(
"standard::*", 0, GLib.PRIORITY_DEFAULT
):
print(info.get_display_name())
task = loop.create_task(list_files())
Example: Download Window with Async Feedback
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is a full example illustrating asynchronous programming with
:mod:`asyncio` for a download window with async feedback.
.. literalinclude:: download_asyncio.py
:language: python
Key Considerations
~~~~~~~~~~~~~~~~~~
* Async tasks use :obj:`GLib.PRIORITY_DEFAULT`. For background tasks,
consider using a lower priority to avoid affecting the responsiveness of
your GTK UI. Please see the `PRIORITY_* GLib Constants
<https://docs.gtk.org/glib/index.html#constants>` for other settings.
* Prefer starting your application using
:obj:`Gio.Application` or :obj:`Gtk.Application` instead of
:func:`asyncio.run`, which is incompatible.
Asynchronous Programming with Callbacks
---------------------------------------
The traditional callback approach is a robust alternative for asynchronous
programming in PyGObject. Consider this example of downloading a web page and
displaying its source in a text field. The operation can also be canceled
mid-execution.
.. literalinclude:: download_callback.py
:language: python
Synchronous Comparison
~~~~~~~~~~~~~~~~~~~~~~
Before exploring the asynchronous method, let’s review the simpler blocking
approach:
.. code-block:: python
file = Gio.File.new_for_uri(
"https://developer.gnome.org/documentation/tutorials/beginners.html"
)
try:
status, contents, etag_out = file.load_contents(None)
except GLib.GError:
print("Error!")
else:
print(contents)
Asynchronous Workflow with Callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For asynchronous tasks, you’ll need:
1. A :class:`Gio.Cancellable` to allow task cancellation.
2. A :class:`Gio.AsyncReadyCallback` function to handle the result upon task
completion.
The example setup includes:
* **Start Button**: The handler calls :meth:`Gio.File.load_contents_async`
with a cancellable and a callback function.
* **Cancel Button**: The handler calls :meth:`Gio.Cancellable.cancel` to stop
the operation.
Once the operation completes—whether successfully, due to an error, or because
it was canceled—the ``on_ready_callback()`` function is invoked. This callback
receives two arguments: the :class:`Gio.File` instance and a
:class:`Gio.AsyncResult` instance containing the operation's result.
To retrieve the result, call :meth:`Gio.File.load_contents_finish`. This method
behaves like :meth:`Gio.File.load_contents`, but since the operation has
already completed, it returns immediately without blocking.
After handling the result, call :meth:`Gio.Cancellable.reset` to prepare the
:class:`Gio.Cancellable` for reuse in future operations. This ensures that the
"Load" button can be clicked again to initiate another task. The application
enforces that only one operation is active at a time by disabling the "Load"
button during an ongoing task using :meth:`Gtk.Widget.set_sensitive`.
|