Python 3.5)でasyncio
を使用する流れを取得していますが、await
ingとは何か、およびすべきことの説明がありません。 「これはIO操作であり、したがってawait
edである必要があります」の観点から、最善の判断をしなければなりませんか?
デフォルトでは、すべてのコードは同期です。 async def
を使用して関数を非同期で定義し、await
を使用してこれらの関数を「呼び出す」ことができます。より正確な質問は、「同期ではなく非同期コードをいつ記述すべきか」です。答えは「あなたがそれから利益を得ることができるとき」です。あなたが指摘したように、ほとんどの場合、あなたは利益を得ます。 I/O操作で作業する場合:
# Synchronous way:
download(url1) # takes 5 sec.
download(url2) # takes 5 sec.
# Total time: 10 sec.
# Asynchronous way:
await asyncio.gather(
async_download(url1), # takes 5 sec.
async_download(url2) # takes 5 sec.
)
# Total time: only 5 sec. (+ little overhead for using asyncio)
もちろん、非同期コードを使用する関数を作成した場合、この関数も非同期である必要があります(async def
として定義する必要があります)。ただし、非同期関数は同期コードを自由に使用できます。何らかの理由なしに同期コードを非同期にキャストしても意味がありません。
# extract_links(url) should be async because it uses async func async_download() inside
async def extract_links(url):
# async_download() was created async to get benefit of I/O
html = await async_download(url)
# parse() doesn't work with I/O, there's no sense to make it async
links = parse(html)
return links
非常に重要なことの1つは、長い同期操作(たとえば、50ミリ秒を超える場合、正確に言うのは難しいなど)によって、その時間のすべての非同期操作がフリーズすることです。
async def extract_links(url):
data = await download(url)
links = parse(data)
# if search_in_very_big_file() takes much time to process,
# all your running async funcs (somewhere else in code) will be frozen
# you need to avoid this situation
links_found = search_in_very_big_file(links)
別のプロセスで実行時間の長い同期関数を呼び出すことを回避できます(結果を待機します)。
executor = ProcessPoolExecutor(2)
async def extract_links(url):
data = await download(url)
links = parse(data)
# Now your main process can handle another async functions while separate process running
links_found = await loop.run_in_executor(executor, search_in_very_big_file, links)
もう1つの例:asyncioでrequests
を使用する必要がある場合。 requests.get
は、同期の長時間実行される関数であり、非同期コード内では呼び出さないでください(これも、フリーズを回避するためです)。しかし、I/Oが原因で長時間実行されています。計算が長いためではありません。その場合、ThreadPoolExecutor
の代わりにProcessPoolExecutor
を使用して、マルチプロセッシングのオーバーヘッドを回避できます。
executor = ThreadPoolExecutor(2)
async def download(url):
response = await loop.run_in_executor(executor, requests.get, url)
return response.text
あなたには多くの自由がありません。関数を呼び出す必要がある場合は、これが通常の関数かコルーチンかを調べる必要があります。呼び出す関数がコルーチンである場合に限り、await
キーワードを使用する必要があります。
async
関数が含まれる場合、これらのasync
関数を調整する「イベントループ」が存在する必要があります。厳密に言うと、それは必要ではありません。値を送信するasync
メソッドを「手動で」実行できますが、おそらく実行したくないでしょう。イベントループは、まだ完了していないコルーチンを追跡し、次のコルーチンを選択して実行を継続します。 asyncio
モジュールはイベントループの実装を提供しますが、これが唯一の可能な実装ではありません。
次の2行のコードを検討してください。
x = get_x()
do_something_else()
そして
x = await aget_x()
do_something_else()
セマンティックはまったく同じです。ある値を生成するメソッドを呼び出し、値が準備できたら変数x
に割り当てて、他のことを行います。どちらの場合も、do_something_else
関数は、前のコード行が終了した後にのみ呼び出されます。非同期aget_x
メソッドの実行前、実行後、または実行中に、コントロールがイベントループに渡されるという意味でもありません。
それでもいくつかの違いがあります:
async
関数内にのみ表示できますaget_x
関数は通常ではありませんが、コルーチンです(これはasync
キーワードで宣言されるか、コルーチンとして装飾されます)aget_x
は、イベントループと「通信」できます。つまり、いくつかのオブジェクトを生成します。イベントループは、これらのオブジェクトをいくつかの操作を行う要求として解釈できる必要があります(つまり、ネットワーク要求を送信して応答を待つか、またはこのコルーチンをn
秒間一時停止するだけです)。通常のget_x
関数は、イベントループと通信できません。