ダミー関数があるとしましょう:
async def foo(arg):
result = await some_remote_call(arg)
return result.upper()
違いは何ですか:
coros = []
for i in range(5):
coros.append(foo(i))
loop = get_event_loop()
loop.run_until_complete(wait(coros))
そして:
from asyncio import ensure_future
futures = []
for i in range(5):
futures.append(ensure_future(foo(i)))
loop = get_event_loop()
loop.run_until_complete(wait(futures))
注:例は結果を返しますが、これは質問の焦点ではありません。戻り値が重要な場合は、gather()
の代わりにwait()
を使用します。
戻り値に関係なく、ensure_future()
の明確さを探しています。 wait(coros)
とwait(futures)
は両方ともコルーチンを実行するので、コルーチンをensure_future
でラップするタイミングと理由は?
基本的に、Python 3.5のasync
を使用して多数のノンブロッキング操作を実行する正しい方法(tm)は何ですか?
余分なクレジットのために、コールをバッチ処理する場合はどうなりますか?たとえば、some_remote_call(...)
を1000回呼び出す必要がありますが、1000の同時接続でWebサーバー/データベース/などをクラッシュさせたくありません。これはスレッドまたはプロセスプールで実行可能ですが、asyncio
でこれを実行する方法はありますか?
https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346 にリンクされたVincentのコメントは、wait()
がensure_future()
のコルーチンをラップすることを示しています!
言い換えれば、未来が必要であり、コルーチンは静かにそれらに変換されます。
コルーチン/未来をバッチ処理する方法の明確な説明を見つけたら、この回答を更新します。
コルーチンは、値を生成することも、外部から値を受け入れることもできるジェネレーター関数です。コルーチンを使用する利点は、関数の実行を一時停止し、後で再開できることです。ネットワーク操作の場合、応答を待っている間、関数の実行を一時停止することは理にかなっています。この時間を使用して、他のいくつかの機能を実行できます。
未来は、JavascriptのPromise
オブジェクトに似ています。これは、将来実現される値のプレースホルダーのようなものです。上記の場合、ネットワークI/Oで待機している間に、関数はコンテナを提供できます。これは、操作が完了するとコンテナに値を入力するという約束です。 futureオブジェクトを保持し、オブジェクトが満たされると、そのメソッドを呼び出して実際の結果を取得できます。
直接的な回答:結果が必要ない場合はensure_future
は必要ありません。結果が必要な場合、または発生した例外を取得する場合に役立ちます。
追加クレジット:run_in_executor
を選択し、Executor
インスタンスを渡して、最大ワーカー数を制御します。
最初の例では、コルーチンを使用しています。 wait
関数は、多数のコルーチンを受け取り、それらを結合します。したがって、wait()
は、すべてのコルーチンが使い果たされると終了します(すべての値を返す/完了する)。
loop = get_event_loop() #
loop.run_until_complete(wait(coros))
run_until_complete
メソッドは、実行が終了するまでループが生きていることを確認します。この場合、非同期実行の結果を取得していないことに注意してください。
2番目の例では、ensure_future
関数を使用してコルーチンをラップし、Task
の一種であるFuture
オブジェクトを返します。 ensure_future
を呼び出すと、コルーチンはメインイベントループで実行されるようにスケジュールされます。返されたfuture/taskオブジェクトにはまだ値がありませんが、時間がたつにつれて、ネットワーク操作が終了すると、futureオブジェクトは操作の結果を保持します。
from asyncio import ensure_future
futures = []
for i in range(5):
futures.append(ensure_future(foo(i)))
loop = get_event_loop()
loop.run_until_complete(wait(futures))
したがって、この例では、コルーチンを使用する代わりにフューチャーを使用していることを除いて、同じことを行っています。
Asyncio/coroutines/futuresの使用方法の例を見てみましょう。
import asyncio
async def slow_operation():
await asyncio.sleep(1)
return 'Future is done!'
def got_result(future):
print(future.result())
# We have result, so let's stop
loop.stop()
loop = asyncio.get_event_loop()
task = loop.create_task(slow_operation())
task.add_done_callback(got_result)
# We run forever
loop.run_forever()
ここでは、loop
オブジェクトでcreate_task
メソッドを使用しました。 ensure_future
は、メインイベントループでタスクをスケジュールします。この方法により、選択したループでコルーチンをスケジュールできます。
また、タスクオブジェクトでadd_done_callback
メソッドを使用してコールバックを追加する概念も確認します。
コルーチンが値を返すか、例外を発生させるか、キャンセルされると、Task
はdone
になります。これらのインシデントを確認する方法があります。
私はこれらのトピックに関するいくつかのブログ投稿を書きました。
もちろん、公式マニュアルの詳細を見つけることができます: https://docs.python.org/3/library/asyncio.html
async def
)を呼び出しても実行されません。ジェネレーター関数がジェネレーターオブジェクトを返すように、コルーチンオブジェクトを返します。await
は、コルーチンから値を取得します。つまり、コルーチンを「呼び出し」ますeusure_future/create_task
は、次の繰り返しのイベントループでコルーチンを実行するようにスケジュールします(ただし、デーモンスレッドのように、終了するまで待機しません)。最初にいくつかの用語を明確にしましょう。
async def
s;await
コルーチンでawait
の2つのコルーチンを作成し、create_task
を使用してもう1つのコルーチンを実行します。
import asyncio
import time
# coroutine function
async def p(Word):
print(f'{time.time()} - {Word}')
async def main():
loop = asyncio.get_event_loop()
coro = p('await') # coroutine
task2 = loop.create_task(p('create_task')) # <- runs in next iteration
await coro # <-- run directly
await task2
if __== "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
結果が得られます:
1539486251.7055213 - await
1539486251.7055705 - create_task
説明:
task1は直接実行され、task2は次の反復で実行されました。
Main関数を置き換えると、別の結果が表示されます。
async def main():
loop = asyncio.get_event_loop()
coro = p('await')
task2 = loop.create_task(p('create_task')) # scheduled to next iteration
await asyncio.sleep(1) # loop got control, and runs task2
await coro # run coro
await task2
結果が得られます:
-> % python coro.py
1539486378.5244057 - create_task
1539486379.5252144 - await # note the delay
説明:
asyncio.sleep(1)
を呼び出すと、コントロールはイベントループに戻され、ループは実行するタスクをチェックしてから、create_task
によって作成されたタスクを実行します。
最初にコルーチン関数を呼び出しますが、await
関数ではないため、単一のコルーチンを作成しただけで、実行はしないことに注意してください。次に、コルーチン関数を再度呼び出して、create_task
呼び出しでラップすると、creat_taskは実際にコルーチンを次の反復で実行するようにスケジュールします。したがって、結果では、create task
がawait
の前に実行されます。
実際、ここでのポイントはループに制御を戻すことです。asyncio.sleep(0)
を使用して同じ結果を確認できます。
loop.create_task
は実際にasyncio.tasks.Task()
を呼び出します。これはloop.call_soon
を呼び出します。そして、loop.call_soon
はタスクをloop._ready
に配置します。ループの各反復中に、loop._readyのすべてのコールバックをチェックして実行します。
asyncio.wait
、asyncio.ensure_future
、およびasyncio.gather
は、実際にloop.create_task
を直接または間接的に呼び出します。
docs にも注意してください:
コールバックは、登録された順に呼び出されます。各コールバックは1回だけ呼び出されます。
これを念頭に置いて、ensure_future
はタスクを作成するための名前として理にかなっています。これは、Futureの結果がawait itであるかどうかにかかわらず計算されるためです。これにより、他のことを待っている間にイベントループがタスクを完了することができます。 Python 3.7 create_task
では、推奨される方法であることに注意してください 将来を保証する 。
注:ここでは、現代のために、Guidoのスライドの「yield from」を「await」に変更しました。