web-dev-qa-db-ja.com

Python 3.5のコルーチンとfuture / taskの違いは?

ダミー関数があるとしましょう:

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でこれを実行する方法はありますか?

89
knite

https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346 にリンクされたVincentのコメントは、wait()ensure_future()のコルーチンをラップすることを示しています!

言い換えれば、未来が必要であり、コルーチンは静かにそれらに変換されます。

コルーチン/未来をバッチ処理する方法の明確な説明を見つけたら、この回答を更新します。

10
knite

コルーチンは、値を生成することも、外部から値を受け入れることもできるジェネレーター関数です。コルーチンを使用する利点は、関数の実行を一時停止し、後で再開できることです。ネットワーク操作の場合、応答を待っている間、関数の実行を一時停止することは理にかなっています。この時間を使用して、他のいくつかの機能を実行できます。

未来は、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メソッドを使用してコールバックを追加する概念も確認します。

コルーチンが値を返すか、例外を発生させるか、キャンセルされると、Taskdoneになります。これらのインシデントを確認する方法があります。

私はこれらのトピックに関するいくつかのブログ投稿を書きました。

もちろん、公式マニュアルの詳細を見つけることができます: https://docs.python.org/3/library/asyncio.html

82
masnun

簡単な答え

  • コルーチン関数(async def)を呼び出しても実行されません。ジェネレーター関数がジェネレーターオブジェクトを返すように、コルーチンオブジェクトを返します。
  • awaitは、コルーチンから値を取得します。つまり、コルーチンを「呼び出し」ます
  • eusure_future/create_taskは、次の繰り返しのイベントループでコルーチンを実行するようにスケジュールします(ただし、デーモンスレッドのように、終了するまで待機しません)。

いくつかのコード例

最初にいくつかの用語を明確にしましょう。

  • コルーチン関数、あなたがasync defs;
  • コルーチンオブジェクト、コルーチン関数を「呼び出す」ときに得られるもの。
  • タスク、イベントループで実行するコルーチンオブジェクトにラップされたオブジェクト。

ケース1、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は次の反復で実行されました。

ケース2、イベントループに制御を渡す

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 taskawaitの前に実行されます。

実際、ここでのポイントはループに制御を戻すことです。asyncio.sleep(0)を使用して同じ結果を確認できます。

フードの下

loop.create_taskは実際にasyncio.tasks.Task()を呼び出します。これはloop.call_soonを呼び出します。そして、loop.call_soonはタスクをloop._readyに配置します。ループの各反復中に、loop._readyのすべてのコールバックをチェックして実行します。

asyncio.waitasyncio.ensure_future、およびasyncio.gatherは、実際にloop.create_taskを直接または間接的に呼び出します。

docs にも注意してください:

コールバックは、登録された順に呼び出されます。各コールバックは1回だけ呼び出されます。

11
ospider

BDFL [2013]から

タスク

  • 未来に包まれたコルーチン
  • クラスTaskはクラスFutureのサブクラスです
  • awaitでも動作します!

  • 裸のコルーチンとどう違うのですか?
  • それを待たずに前進することができます
    • 他の何かを待つ限り、つまり
      • await [something_else]

これを念頭に置いて、ensure_futureはタスクを作成するための名前として理にかなっています。これは、Futureの結果がawait itであるかどうかにかかわらず計算されるためです。これにより、他のことを待っている間にイベントループがタスクを完了することができます。 Python 3.7 create_taskでは、推奨される方法であることに注意してください 将来を保証する

注:ここでは、現代のために、Guidoのスライドの「yield from」を「await」に変更しました。

1
crizCraig