非同期サポート—Djangoドキュメント

提供:Dev Guides
< DjangoDjango/docs/3.0.x/topics/async
移動先:案内検索

非同期サポート

バージョン3.0の新機能。


Djangoは非同期(「非同期」)Pythonのサポートを開発していますが、非同期ビューまたはミドルウェアはまだサポートしていません。 それらは将来のリリースで提供される予定です。

非同期エコシステムの他の部分のサポートは限られています。 つまり、Djangoは ASGI とネイティブに話すことができ、いくつかの非同期安全サポートがあります。

非同期の安全性

Djangoの特定の重要な部分は、コルーチンに対応していないグローバル状態であるため、非同期環境で安全に動作できません。 Djangoのこれらの部分は「非同期非安全」として分類され、非同期環境での実行から保護されています。 ORMが主な例ですが、この方法で保護されている他の部分もあります。

実行中のイベントループがあるスレッドからこれらの部分のいずれかを実行しようとすると、 SynchronousOnlyOperation エラーが発生します。 このエラーが発生するために、非同期関数内に直接いる必要はないことに注意してください。 sync_to_async()やスレッドプールなどを経由せずに非同期関数から直接同期関数を呼び出した場合は、コードがまだ非同期コンテキストで実行されているため、同期関数が発生する可能性もあります。

このエラーが発生した場合は、非同期コンテキストから問題のあるコードを呼び出さないようにコードを修正する必要があります。 代わりに、独自の同期関数でasync-unsafeと通信するコードを記述し、 asgiref.sync.sync_to_async()、または独自のスレッドで同期コードを実行するその他の好ましい方法を使用して呼び出します。

あなたが絶対にで、非同期コンテキストからこのコードを実行する必要がある場合-たとえば、外部環境によって強制されており、同時に実行される可能性はないと確信しています(例えば Jupyter ノートブックを使用している場合は、DJANGO_ALLOW_ASYNC_UNSAFE環境変数を使用して警告を無効にできます。

警告

このオプションを有効にして、Djangoの非同期で安全でない部分への同時アクセスがある場合、データの損失または破損が発生する可能性があります。 非常に注意して、本番環境では使用しないでください。


Python内からこれを行う必要がある場合は、os.environを使用して行います。

os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

非同期アダプター機能

非同期コンテキストから同期コードを呼び出す場合、またはその逆の場合は、呼び出しスタイルを調整する必要があります。 このために、asgiref.syncパッケージから利用できる2つのアダプター関数があります: async_to_sync()sync_to_async()。 これらは、互換性を維持しながら、同期呼び出しスタイルと非同期呼び出しスタイルの間を移行するために使用されます。

これらのアダプター関数は、Djangoで広く使用されています。 asgiref パッケージ自体はDjangoプロジェクトの一部であり、pipを使用してDjangoをインストールすると、依存関係として自動的にインストールされます。

async_to_sync()

async_to_sync(async_function, force_new_loop=False)

非同期関数をラップし、代わりに同期関数を返します。 直接ラッパーまたはデコレータとして使用できます。

from asgiref.sync import async_to_sync

sync_function = async_to_sync(async_function)

@async_to_sync
async def async_function(...):
    ...

非同期関数は、現在のスレッドが存在する場合、そのイベントループで実行されます。 現在のイベントループがない場合は、非同期関数専用の新しいイベントループがスピンアップされ、完了すると再びシャットダウンされます。 どちらの状況でも、非同期関数は呼び出し元のコードとは異なるスレッドで実行されます。

Threadlocalsとcontextvarsの値は、境界を越えて両方向に保持されます。

async_to_sync()は本質的に、Pythonの標準ライブラリで利用可能なasyncio.run()関数のより強力なバージョンです。 スレッドローカルが機能することを保証するだけでなく、そのラッパーがその下で使用される場合、 sync_to_async()thread_sensitiveモードも有効にします。


sync_to_async()

sync_to_async(sync_function, thread_sensitive=False)

同期関数をラップし、代わりに非同期(待機可能)関数を返します。 直接ラッパーまたはデコレータとして使用できます。

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とcontextvarsの値は、境界を越えて両方向に保持されます。

同期関数は、すべてメインスレッドで実行されると想定して記述される傾向があるため、 sync_to_async()には2つのスレッドモードがあります。

スレッドセンシティブモードは非常に特殊であり、すべての関数を同じスレッドで実行するために多くの作業を行います。 ただし、は、スタックの上の async_to_sync() の使用に依存して、メインスレッドで正しく実行されることに注意してください。 asyncio.run()(または代わりに他のオプション)を使用すると、単一の共有スレッド(メインスレッドではない)でスレッドセンシティブ関数を実行するだけにフォールバックします。

これがDjangoで必要な理由は、多くのライブラリ、特にデータベースアダプターでは、作成されたのと同じスレッドでアクセスする必要があり、既存のDjangoコードの多くは、すべてが同じスレッドで実行されると想定しているためです(例: 後でビューで使用するためにリクエストに物事を追加するミドルウェア)。

このコードとの潜在的な互換性の問題を導入するのではなく、代わりにこのモードを追加して、既存のすべてのDjango同期コードが同じスレッドで実行され、非同期モードと完全に互換性があるようにしました。 同期コードは、それを呼び出す非同期コードとは常に異なるスレッドにあるため、新しいコードで生のデータベースハンドルやその他のスレッド依存の参照を渡さないようにする必要があります。