非同期プログラミングは、Python 3.5以降、キーワードasyncとawaitの導入により言語に登場しました。初めは、非同期タスクにはasyncioやコルーチンベースのジェネレーターなどのライブラリが使用されており、理解と保守が難しいものでした。async/awaitの構文の登場により、非同期コードはより明示的で読みやすく、従来の同期スタイルに近いものになりました。
async/awaitの登場前は、非同期処理はコールバックやジェネレーター(例えば、tornadoライブラリや古いAPI asyncioを利用)を通じて実装されていました。このようなコードはデバッグと保守が困難でした。
大規模なI/O操作(ネットワークリクエスト、ファイルの入出力)を同期コードで処理する際の主な問題は、メインスレッドのブロックです。これによりパフォーマンスが低下し、リソースの効果的な使用が不可能になります。
async/awaitを用いた非同期プログラミングは、単一スレッド内で多数の入出力操作を並行して実行することを可能にし、ブロックを回避します。この際、構文が通常の関数に近いため、可読性とデバッグが容易になります。
import asyncio async def fetch_data(delay): print(f"{delay}秒の遅延後に取得を開始します") await asyncio.sleep(delay) print(f"{delay}秒の遅延後に取得が完了しました") return delay async def main(): results = await asyncio.gather( fetch_data(1), fetch_data(2), fetch_data(3) ) print("結果:", results) asyncio.run(main())
async defを用いて定義された関数は通常の関数として呼び出せますか?
いいえ。そのような関数を呼び出すとコルーチンオブジェクトが返され、イベントループに渡されるまでは実行されません(例えば、awaitまたはasyncio.run()を使って)。
def foo(): return 42 async def bar(): return 42 print(foo()) # 42 print(bar()) # <coroutine object bar at ...>
非同期関数の外でawaitを使うことはできますか?
いいえ。awaitキーワードはasync defで宣言された関数の内部でのみ使用する必要があります。そのような関数の外でawaitを使用しようとすると、SyntaxErrorが発生します。
# エラー! await asyncio.sleep(1) # SyntaxError: 'await' outside async function
I/Oに関連しない操作(例えば計算)に対して非同期処理は機能しますか?
いいえ。非同期処理は入出力操作に対してのみ効果的です。計算タスクには依然としてmultiprocessingまたはthreadingが必要で、そうでないとイベントループがブロックされます。
利点:
欠点:
ネガティブケース: 若い開発者たちがasync/awaitを使ってアプリケーションを速くしようとしましたが、非同期に行っていたのは計算だけで、ネットワークリクエストではありませんでした。アプリケーションは速くなりませんでした。利点:構文に慣れた。欠点:利益なし、コードが複雑化。
ポジティブケース: APIへの何千ものリクエストを非同期に処理しました。サーバーはリソースを増やさずにより多くのクライアントにサービスを提供できるようになりました。利点:パフォーマンスが大幅に向上し、アーキテクチャが簡素化されました。欠点:初心者にとって参加のハードルが上がりました。