Runners¶
Source code: Lib/asyncio/runners.py
This section outlines high-level asyncio primitives to run asyncio code.
They are built on top of an event loop with the aim to simplify async code usage for common wide-spread scenarios.
Running an asyncio Program¶
- asyncio.run(coro, *, debug=None)¶
Execute the coroutine coro and return the result.
This function runs the passed coroutine, taking care of managing the asyncio event loop, finalizing asynchronous generators, and closing the threadpool.
This function cannot be called when another asyncio event loop is running in the same thread.
If debug is
True
, the event loop will be run in debug mode.False
disables debug mode explicitly.None
is used to respect the global Debug Mode settings.This function always creates a new event loop and closes it at the end. It should be used as a main entry point for asyncio programs, and should ideally only be called once.
Example:
async def main(): await asyncio.sleep(1) print('hello') asyncio.run(main())
New in version 3.7.
Changed in version 3.9: Updated to use
loop.shutdown_default_executor()
.Changed in version 3.10: debug is
None
by default to respect the global debug mode settings.
Runner context manager¶
- class asyncio.Runner(*, debug=None, loop_factory=None)¶
A context manager that simplifies multiple async function calls in the same context.
Sometimes several top-level async functions should be called in the same event loop and
contextvars.Context
.If debug is
True
, the event loop will be run in debug mode.False
disables debug mode explicitly.None
is used to respect the global Debug Mode settings.loop_factory could be used for overriding the loop creation. It is the responsibility of the loop_factory to set the created loop as the current one. By default
asyncio.new_event_loop()
is used and set as current event loop withasyncio.set_event_loop()
if loop_factory isNone
.Basically,
asyncio.run()
example can be rewritten with the runner usage:async def main(): await asyncio.sleep(1) print('hello') with asyncio.Runner() as runner: runner.run(main())
New in version 3.11.
- run(coro, *, context=None)¶
Run a coroutine coro in the embedded loop.
Return the coroutine’s result or raise its exception.
An optional keyword-only context argument allows specifying a custom
contextvars.Context
for the coro to run in. The runner’s default context is used ifNone
.This function cannot be called when another asyncio event loop is running in the same thread.
- close()¶
Close the runner.
Finalize asynchronous generators, shutdown default executor, close the event loop and release embedded
contextvars.Context
.
- get_loop()¶
Return the event loop associated with the runner instance.
Note
Runner
uses the lazy initialization strategy, its constructor doesn’t initialize underlying low-level structures.Embedded loop and context are created at the
with
body entering or the first call ofrun()
orget_loop()
.
Handling Keyboard Interruption¶
New in version 3.11.
When signal.SIGINT
is raised by Ctrl-C, KeyboardInterrupt
exception is raised in the main thread by default. However this doesn’t work with
asyncio
because it can interrupt asyncio internals and can hang the program from
exiting.
To mitigate this issue, asyncio
handles signal.SIGINT
as follows:
asyncio.Runner.run()
installs a customsignal.SIGINT
handler before any user code is executed and removes it when exiting from the function.The
Runner
creates the main task for the passed coroutine for its execution.When
signal.SIGINT
is raised by Ctrl-C, the custom signal handler cancels the main task by callingasyncio.Task.cancel()
which raisesasyncio.CancelledError
inside the main task. This causes the Python stack to unwind,try/except
andtry/finally
blocks can be used for resource cleanup. After the main task is cancelled,asyncio.Runner.run()
raisesKeyboardInterrupt
.A user could write a tight loop which cannot be interrupted by
asyncio.Task.cancel()
, in which case the second following Ctrl-C immediately raises theKeyboardInterrupt
without cancelling the main task.