非同期サポート¶
Django は非同期 ("async") Python の試験的サポートをしていますが、ビューやミドルウェアの非同期サポートはなされていません。今後のリリースでサポートされる予定です。
async エコシステムの他の部分のサポートは限定的です。つまり、 Django はネイティブで ASGI を話し、部分的に非同期安全性をサポートしています。
非同期安全性¶
Django の特定のキーパーツは、コルーチンが感知できないグローバルステートを持つため、非同期の環境では安全に操作できません。これらの Django のパーツは、"async-unsafe" として分類されており、非同期な環境での実行から保護されています。ORM は主な例ですが、この他にもこのように保護されているパーツがあります。
If you try to run any of these parts from a thread where there is a running
event loop, you will get a
SynchronousOnlyOperation
error. Note that you
don't have to be inside an async function directly to have this error occur. If
you have called a synchronous function directly from an asynchronous function
without going through something like sync_to_async()
or a threadpool,
then it can also occur, as your code is still running in an asynchronous
context.
If you encounter this error, you should fix your code to not call the offending
code from an async context; instead, write your code that talks to async-unsafe
in its own, synchronous function, and call that using
asgiref.sync.sync_to_async()
, or any other preferred way of running
synchronous code in its own thread.
もしこのコードを 絶対に 切実に非同期コンテキストから実行する必要がある場合 - 例えば、外部の環境に強制されていて、同時に実行される可能性が確実にない場合 (例: Jupyter notebook の中など)、環境変数 DJANGO_ALLOW_ASYNC_UNSAFE
で警告を無効化できます。
警告
このオプションを有効にした上で、Djangoの async-unsafe パーツへ同時アクセスがあると、データが失われたり壊れたりする可能性があります。十分な注意を払い、本番環境では使用しないでください。
もし、これをPython内部から行いたい場合は、 os.environ
:: を使用してください。
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
Async adapter functions¶
It is necessary to adapt the calling style when calling synchronous code from
an asynchronous context, or vice-versa. For this there are two adapter
functions, made available from the asgiref.sync
package:
async_to_sync()
and sync_to_async()
. They are used to transition
between sync and async calling styles while preserving compatibility.
These adapter functions are widely used in Django. The asgiref package
itself is part of the Django project, and it is automatically installed as a
dependency when you install Django with pip
.
async_to_sync()
¶
-
async_to_sync
(async_function, force_new_loop=False)¶
Wraps an asynchronous function and returns a synchronous function in its place. Can be used as either a direct wrapper or a decorator:
from asgiref.sync import async_to_sync
sync_function = async_to_sync(async_function)
@async_to_sync
async def async_function(...):
...
The asynchronous function is run in the event loop for the current thread, if one is present. If there is no current event loop, a new event loop is spun up specifically for the async function and shut down again once it completes. In either situation, the async function will execute on a different thread to the calling code.
Threadlocals and contextvars values are preserved across the boundary in both directions.
async_to_sync()
is essentially a more powerful version of the
asyncio.run()
function available in Python's standard library. As well
as ensuring threadlocals work, it also enables the thread_sensitive
mode of
sync_to_async()
when that wrapper is used below it.
sync_to_async()
¶
-
sync_to_async
(sync_function, thread_sensitive=False)¶
Wraps a synchronous function and returns an asynchronous (awaitable) function in its place. Can be used as either a direct wrapper or a decorator:
from asgiref.sync import sync_to_async
async_function = sync_to_async(sync_function)
async_function = sync_to_async(sensitive_sync_function, thread_sensitive=True)
@sync_to_async
def sync_function(...):
...
Threadlocals and contextvars values are preserved across the boundary in both directions.
Synchronous functions tend to be written assuming they all run in the main
thread, so sync_to_async()
has two threading modes:
thread_sensitive=False
(the default): the synchronous function will run in a brand new thread which is then closed once it completes.thread_sensitive=True
: the synchronous function will run in the same thread as all otherthread_sensitive
functions, and this will be the main thread, if the main thread is synchronous and you are using theasync_to_sync()
wrapper.
Thread-sensitive mode is quite special, and does a lot of work to run all
functions in the same thread. Note, though, that it relies on usage of
async_to_sync()
above it in the stack to correctly run things on the
main thread. If you use asyncio.run()
(or other options instead), it will
fall back to just running thread-sensitive functions in a single, shared thread
(but not the main thread).
The reason this is needed in Django is that many libraries, specifically database adapters, require that they are accessed in the same thread that they were created in, and a lot of existing Django code assumes it all runs in the same thread (e.g. middleware adding things to a request for later use by a view).
Rather than introduce potential compatibility issues with this code, we instead opted to add this mode so that all existing Django synchronous code runs in the same thread and thus is fully compatible with asynchronous mode. Note, that synchronous code will always be in a different thread to any async code that is calling it, so you should avoid passing raw database handles or other thread-sensitive references around in any new code you write.