Developing with asyncio¶
Asynchronous programming is different from classic “sequential”programming.
This page lists common mistakes and traps and explains howto avoid them.
Debug Mode¶
By default asyncio runs in production mode. In order to easethe development asyncio has adebug mode.
There are several ways to enable asyncio debug mode:
Setting the
PYTHONASYNCIODEBUG
environment variable to1
.Using thePython Development Mode.
Passing
debug=True
toasyncio.run()
.Calling
loop.set_debug()
.
In addition to enabling the debug mode, consider also:
setting the log level of theasyncio logger to
logging.DEBUG
, for example the following snippet of codecan be run at startup of the application:logging.basicConfig(level=logging.DEBUG)
configuring the
warnings
module to displayResourceWarning
warnings. One way of doing that is byusing the-W
default
command line option.
When the debug mode is enabled:
Many non-threadsafe asyncio APIs (such as
loop.call_soon()
andloop.call_at()
methods) raise an exception if they are calledfrom a wrong thread.The execution time of the I/O selector is logged if it takes too long toperform an I/O operation.
Callbacks taking longer than 100 milliseconds are logged. The
loop.slow_callback_duration
attribute can be used to set theminimum execution duration in seconds that is considered “slow”.
Concurrency and Multithreading¶
An event loop runs in a thread (typically the main thread) and executesall callbacks and Tasks in its thread. While a Task is running in theevent loop, no other Tasks can run in the same thread. When a Taskexecutes anawait
expression, the running Task gets suspended, andthe event loop executes the next Task.
To schedule acallback from another OS thread, theloop.call_soon_threadsafe()
method should be used. Example:
loop.call_soon_threadsafe(callback,*args)
Almost all asyncio objects are not thread safe, which is typicallynot a problem unless there is code that works with them from outsideof a Task or a callback. If there’s a need for such code to call alow-level asyncio API, theloop.call_soon_threadsafe()
methodshould be used, e.g.:
loop.call_soon_threadsafe(fut.cancel)
To schedule a coroutine object from a different OS thread, therun_coroutine_threadsafe()
function should be used. It returns aconcurrent.futures.Future
to access the result:
asyncdefcoro_func():returnawaitasyncio.sleep(1,42)# Later in another OS thread:future=asyncio.run_coroutine_threadsafe(coro_func(),loop)# Wait for the result:result=future.result()
To handle signals the event loop must berun in the main thread.
Theloop.run_in_executor()
method can be used with aconcurrent.futures.ThreadPoolExecutor
to executeblocking code in a different OS thread without blocking the OS threadthat the event loop runs in.
There is currently no way to schedule coroutines or callbacks directlyfrom a different process (such as one started withmultiprocessing
). TheEvent Loop Methodssection lists APIs that can read from pipes and watch file descriptorswithout blocking the event loop. In addition, asyncio’sSubprocess APIs provide a way to start aprocess and communicate with it from the event loop. Lastly, theaforementionedloop.run_in_executor()
method can also be usedwith aconcurrent.futures.ProcessPoolExecutor
to executecode in a different process.
Running Blocking Code¶
Blocking (CPU-bound) code should not be called directly. For example,if a function performs a CPU-intensive calculation for 1 second,all concurrent asyncio Tasks and IO operations would be delayedby 1 second.
An executor can be used to run a task in a different thread or even ina different process to avoid blocking the OS thread with theevent loop. See theloop.run_in_executor()
method for moredetails.
Logging¶
asyncio uses thelogging
module and all logging is performedvia the"asyncio"
logger.
The default log level islogging.INFO
, which can be easilyadjusted:
logging.getLogger("asyncio").setLevel(logging.WARNING)
Network logging can block the event loop. It is recommended to usea separate thread for handling logs or use non-blocking IO. For example,seeDealing with handlers that block.
Detect never-awaited coroutines¶
When a coroutine function is called, but not awaited(e.g.coro()
instead ofawaitcoro()
)or the coroutine is not scheduled withasyncio.create_task()
, asynciowill emit aRuntimeWarning
:
importasyncioasyncdeftest():print("never scheduled")asyncdefmain():test()asyncio.run(main())
Output:
test.py:7:RuntimeWarning:coroutine'test'wasneverawaitedtest()
Output in debug mode:
test.py:7:RuntimeWarning:coroutine'test'wasneverawaitedCoroutinecreatedat(mostrecentcalllast)File"../t.py",line9,in<module>asyncio.run(main(),debug=True)<..>File"../t.py",line7,inmaintest()test()
The usual fix is to either await the coroutine or call theasyncio.create_task()
function:
asyncdefmain():awaittest()
Detect never-retrieved exceptions¶
If aFuture.set_exception()
is called but the Future object isnever awaited on, the exception would never be propagated to theuser code. In this case, asyncio would emit a log message when theFuture object is garbage collected.
Example of an unhandled exception:
importasyncioasyncdefbug():raiseException("not consumed")asyncdefmain():asyncio.create_task(bug())asyncio.run(main())
Output:
Taskexceptionwasneverretrievedfuture:<Taskfinishedcoro=<bug()done,definedattest.py:3>exception=Exception('not consumed')>Traceback(mostrecentcalllast):File"test.py",line4,inbugraiseException("not consumed")Exception:notconsumed
Enable the debug mode to get thetraceback where the task was created:
asyncio.run(main(),debug=True)
Output in debug mode:
Taskexceptionwasneverretrievedfuture:<Taskfinishedcoro=<bug()done,definedattest.py:3>exception=Exception('not consumed')createdatasyncio/tasks.py:321>source_traceback:Objectcreatedat(mostrecentcalllast):File"../t.py",line9,in<module>asyncio.run(main(),debug=True)<..>Traceback(mostrecentcalllast):File"../t.py",line4,inbugraiseException("not consumed")Exception:notconsumed