非同期サポート
Djangoは、 ASGI で実行している場合、完全に非同期対応のリクエストスタックとともに、非同期(「非同期」)ビューの記述をサポートしています。 非同期ビューは引き続きWSGIで機能しますが、パフォーマンスが低下し、効率的な長時間実行要求を実行する機能がありません。
ORMおよびDjangoの他の部分の非同期サポートに引き続き取り組んでいます。 これは将来のリリースで見られると期待できます。 今のところ、 sync_to_async()アダプターを使用して、Djangoの同期部分と対話できます。 統合できる非同期ネイティブPythonライブラリもすべてあります。
バージョン3.1で変更:非同期ビューのサポートが追加されました。
非同期ビュー
バージョン3.1の新機能。
ビューの呼び出し可能部分がコルーチンを返すようにすることで、任意のビューを非同期として宣言できます。通常、これはasync def
を使用して行われます。 関数ベースのビューの場合、これはasync def
を使用してビュー全体を宣言することを意味します。 クラスベースのビューの場合、これは、__call__()
メソッドをasync def
(__init__()
またはas_view()
ではない)にすることを意味します。
ノート
Djangoはasyncio.iscoroutinefunction
を使用して、ビューが非同期であるかどうかをテストします。 コルーチンを返す独自のメソッドを実装する場合は、ビューの_is_coroutine
属性をasyncio.coroutines._is_coroutine
に設定して、この関数がTrue
を返すようにしてください。
WSGIサーバーでは、非同期ビューは独自の1回限りのイベントループで実行されます。 つまり、同時非同期HTTPリクエストなどの非同期機能を問題なく使用できますが、非同期スタックのメリットは得られません。
主な利点は、Pythonスレッドを使用せずに何百もの接続を処理できることです。 これにより、低速ストリーミング、長時間ポーリング、およびその他のエキサイティングな応答タイプを使用できます。
これらを使用する場合は、代わりに ASGI を使用してDjangoをデプロイする必要があります。
警告
同期ミドルウェアがサイトにロードされていない場合にのみ、完全に非同期のリクエストスタックのメリットを享受できます。 同期ミドルウェアがある場合、Djangoはリクエストごとにスレッドを使用して、同期環境を安全にエミュレートする必要があります。
ミドルウェアは、同期コンテキストと非同期コンテキストの両方をサポートするように構築できます。 Djangoのミドルウェアの一部はこのように構築されていますが、すべてではありません。 ミドルウェアDjangoが適応する必要があるものを確認するには、django.request
ロガーのデバッグログをオンにして、「同期ミドルウェア…適応」に関するログメッセージを探します。
ASGIモードとWSGIモードの両方で、非同期サポートを安全に使用して、コードをシリアルではなく同時に実行できます。 これは、外部APIまたはデータストアを処理する場合に特に便利です。
ORMのように、まだ同期しているDjangoの一部を呼び出す場合は、 sync_to_async()呼び出しでラップする必要があります。 例えば:
from asgiref.sync import sync_to_async
results = await sync_to_async(Blog.objects.get, thread_sensitive=True)(pk=123)
ORMコードを独自の関数に移動し、 sync_to_async()を使用してその関数全体を呼び出す方が簡単な場合があります。 例えば:
from asgiref.sync import sync_to_async
def _get_blog(pk):
return Blog.objects.select_related('author').get(pk=pk)
get_blog = sync_to_async(_get_blog, thread_sensitive=True)
非同期ビューからのみ同期しているDjangoの一部を誤って呼び出そうとすると、Djangoの非同期安全保護をトリガーして、データを破損から保護します。
パフォーマンス
ビューと一致しないモードで実行している場合(例: WSGIでの非同期ビュー、またはASGIでの従来の同期ビュー)、Djangoは、コードを実行できるように、他の呼び出しスタイルをエミュレートする必要があります。 このコンテキストスイッチにより、約1ミリ秒の小さなパフォーマンスペナルティが発生します。
これはミドルウェアにも当てはまります。 Djangoは、同期と非同期の間のコンテキストスイッチの数を最小限に抑えようとします。 ASGIサーバーがあり、すべてのミドルウェアとビューが同期している場合、ミドルウェアスタックに入る前に、サーバーは1回だけ切り替わります。
ただし、ASGIサーバーと非同期ビューの間に同期ミドルウェアを配置する場合は、ミドルウェアの同期モードに切り替えてから、ビューの非同期モードに戻す必要があります。 Djangoは、ミドルウェア例外の伝播のために同期スレッドも開いたままにします。 これは最初は目立たないかもしれませんが、リクエストごとに1スレッドというこのペナルティを追加すると、非同期パフォーマンスの利点がなくなる可能性があります。
ASGIとWSGIがコードにどのような影響を与えるかを確認するには、独自のパフォーマンステストを行う必要があります。 場合によっては、要求処理コードがすべて非同期で実行されているため、ASGIでの純粋な同期コードベースでもパフォーマンスが向上することがあります。 一般に、プロジェクトに非同期コードがある場合にのみ、ASGIモードを有効にする必要があります。
非同期の安全性
- DJANGO_ALLOW_ASYNC_UNSAFE
Djangoの特定の重要な部分は、コルーチンに対応していないグローバル状態であるため、非同期環境で安全に動作できません。 Djangoのこれらの部分は「async-unsafe」として分類され、非同期環境での実行から保護されています。 ORMが主な例ですが、この方法で保護されている他の部分もあります。
実行中のイベントループがあるスレッドからこれらの部分のいずれかを実行しようとすると、 SynchronousOnlyOperation エラーが発生します。 このエラーが発生するために、非同期関数内に直接いる必要はないことに注意してください。 sync_to_async()などを使用せずに、非同期関数から直接同期関数を呼び出した場合も発生する可能性があります。 これは、非同期コードとして宣言されていない場合でも、コードがアクティブなイベントループを持つスレッドで実行されているためです。
このエラーが発生した場合は、非同期コンテキストから問題のあるコードを呼び出さないようにコードを修正する必要があります。 代わりに、非同期の安全でない関数と通信するコードを独自のsync関数で記述し、 asgiref.sync.sync_to_async()(または独自のスレッドで同期コードを実行する他の方法)を使用して呼び出します。 。
非同期コンテキストは、Djangoコードを実行している環境によって課せられる可能性があります。 たとえば、 Jupyter ノートブックと IPython インタラクティブシェルはどちらもアクティブなイベントループを透過的に提供するため、非同期APIとのやり取りが簡単になります。
IPythonシェルを使用している場合は、次のコマンドを実行してこのイベントループを無効にできます。
%autoawait off
IPythonプロンプトのコマンドとして。 これにより、 SynchronousOnlyOperation エラーを生成せずに同期コードを実行できます。 ただし、非同期APIをawait
することもできなくなります。 イベントループをオンに戻すには、次のコマンドを実行します。
%autoawait on
IPython以外の環境にいる場合(または何らかの理由でIPythonでautoawait
をオフにできない場合)、特定のコードが同時に実行される可能性はありません。 、そして絶対に非同期コンテキストから同期コードを実行する必要がある場合は、 DJANGO_ALLOW_ASYNC_UNSAFE 環境変数を任意の値に設定して警告を無効にできます。
警告
このオプションを有効にして、Djangoの非同期で安全でない部分への同時アクセスがある場合、データの損失または破損が発生する可能性があります。 非常に注意して、本番環境では使用しないでください。
Python内からこれを行う必要がある場合は、os.environ
を使用して行います。
import os
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
非同期アダプター機能
非同期コンテキストから同期コードを呼び出す場合、またはその逆の場合は、呼び出しスタイルを調整する必要があります。 このために、asgiref.sync
モジュールから、 async_to_sync()と sync_to_async()の2つのアダプター関数があります。 これらは、互換性を維持しながら、呼び出しスタイル間を移行するために使用されます。
これらのアダプター関数は、Djangoで広く使用されています。 asgiref パッケージ自体はDjangoプロジェクトの一部であり、pip
を使用してDjangoをインストールすると、依存関係として自動的にインストールされます。
async_to_sync()
- async_to_sync(async_function, force_new_loop=False)
非同期関数を受け取り、それをラップする同期関数を返します。 直接ラッパーまたはデコレータとして使用できます。
from asgiref.sync import async_to_sync
async def get_data(...):
...
sync_get_data = async_to_sync(get_data)
@async_to_sync
async def get_other_data(...):
...
非同期関数は、現在のスレッドが存在する場合、そのイベントループで実行されます。 現在のイベントループがない場合は、単一の非同期呼び出し専用に新しいイベントループがスピンアップされ、完了すると再びシャットダウンされます。 どちらの状況でも、非同期関数は呼び出し元のコードとは異なるスレッドで実行されます。
Threadlocalsとcontextvarsの値は、境界を越えて両方向に保持されます。
async_to_sync()は、基本的に、Pythonの標準ライブラリにあるasyncio.run()
関数のより強力なバージョンです。 スレッドローカルが機能することを保証するだけでなく、そのラッパーがその下で使用される場合、 sync_to_async()のthread_sensitive
モードも有効にします。
sync_to_async()
- sync_to_async(sync_function, thread_sensitive=True)
同期関数を受け取り、それをラップする非同期関数を返します。 直接ラッパーまたはデコレータとして使用できます。
from asgiref.sync import sync_to_async
async_function = sync_to_async(sync_function, thread_sensitive=False)
async_function = sync_to_async(sensitive_sync_function, thread_sensitive=True)
@sync_to_async
def sync_function(...):
...
Threadlocalsとcontextvarsの値は、境界を越えて両方向に保持されます。
同期関数は、すべてメインスレッドで実行されると想定して記述される傾向があるため、 sync_to_async()には2つのスレッドモードがあります。
thread_sensitive=True
(デフォルト):同期機能は、他のすべてのthread_sensitive
機能と同じスレッドで実行されます。 メインスレッドが同期していて、 async_to_sync()ラッパーを使用している場合、これがメインスレッドになります。thread_sensitive=False
:同期機能は新しいスレッドで実行され、呼び出しが完了すると閉じられます。
警告
asgiref
バージョン3.3.0は、thread_sensitive
パラメーターのデフォルト値をTrue
に変更しました。 これはより安全なデフォルトであり、多くの場合Djangoと正しい値をやり取りしますが、以前のバージョンからasgiref
を更新する場合は、必ずsync_to_async()
の使用を評価してください。
スレッドセンシティブモードは非常に特殊であり、すべての関数を同じスレッドで実行するために多くの作業を行います。 ただし、は、スタックの上の async_to_sync() の使用に依存して、メインスレッドで正しく実行されることに注意してください。 asyncio.run()
などを使用すると、単一の共有スレッドでスレッドセンシティブ関数を実行するようにフォールバックしますが、これはメインスレッドではありません。
これがDjangoで必要な理由は、多くのライブラリ、特にデータベースアダプターでは、作成されたのと同じスレッドでアクセスする必要があるためです。 また、既存のDjangoコードの多くは、すべてが同じスレッドで実行されることを前提としています。 後でビューで使用するためにリクエストに物事を追加するミドルウェア。
このコードとの潜在的な互換性の問題を導入するのではなく、代わりにこのモードを追加して、既存のすべてのDjango同期コードが同じスレッドで実行され、非同期モードと完全に互換性があるようにしました。 同期コードは、それを呼び出す非同期コードとは常に異なるスレッドにあるため、生のデータベースハンドルやその他のスレッドに依存する参照を渡さないようにする必要があります。