1. maueki

    No comment

    maueki
Changes in body
Source | HTML | Preview

ここは書きかけ

C#においてはasyncは内部にawaitを書けるという以外何もしないが(要出典)、pythonのasync defdefとは機能が異なっている。

hello.py
import asyncio

async def hello_world():
    print("Hello World!")

hello_world()

$ python3 hello.py
hello.py:6: RuntimeWarning: coroutine 'hello_world' was never awaited
  hello_world()

pythonにおいてasync defコルーチン関数を定義するための構文であり 、コルーチンはイベントループ内でしか実行されない

await futureawait coroutine

18.5.3.1. コルーチンを読んでいると気になる記述に出くわす。

コルーチンができること:

  • result = await future or result = yield from future -- suspends the coroutine until the future is done, then returns the future's result, or raises an exception, which will be propagated. (If the future is cancelled, it will raise a CancelledError exception.) Note that tasks are futures, and everything said about futures also applies to tasks.
  • result = await coroutine or result = yield from coroutine -- wait for another coroutine to produce a result (or raise an exception, which will be propagated). The coroutine expression must be a call to another coroutine.

どうやらpythonのawaitfuturecoroutineの2つが取れるらしいが、挙動が微妙に違う。
await futureの方はsuspends、つまり制御を手放すのに対してawait coroutineの方は単にcoroutineの終了を待つだけに見える。

ためしに以下のようなコードを書いてみる。

await_sample.py
import asyncio
import time

async def task_one():
    print("task_one: before sleep")
    await asyncio.sleep(0.1)
    print("task_one: after sleep")
    return 1

async def time_sleep():
    time.sleep(5)
    print("time_sleep")

async def task_two():
    print("task_two: before task_one")
    await time_sleep()
    print("task_two: after task_one")
    return 2

async def test(loop):
    t1 = loop.create_task(task_one())
    t2 = loop.create_task(task_two())

    print(repr(await t1))
    print(repr(await t2))

def main():
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(test(loop))
    finally:
        loop.close()

main()
$ python3 await_sample.py
task_one: before sleep
task_two: before task_one
# ここで5秒待つ
time_sleep
task_two: after task_one
task_one: after sleep
1
2

挙動としては、一見以下のように見える

  1. task_one()が実行され、await asyncio.sleep(0.1)で制御を手放す
  2. task_two()が開始されるがawait time_sleep()では制御を手放さずそのまま寝て返ってくる

asyncio.sleep()コルーチンである。また、time_sleep()もコルーチンのはずである。片方は制御を手放し、片方は制御を手放さない。これはどういうことだろうか?

ポイントはasyncio.sleep()の実装にある。

cpython/Lib/asyncio/tasks.py
async def sleep(delay, result=None, *, loop=None):
    """Coroutine that completes after a given time (in seconds)."""
    if delay <= 0:
        await __sleep0()
        return result

    if loop is None:
        loop = events.get_event_loop()
    future = loop.create_future()
    h = loop.call_later(delay,
                        futures._set_result_unless_cancelled,
                        future, result)
    try:
        return await future
    finally:
        h.cancel()

return await futureがあるのが見て取れる。
冒頭の記述を思い出して解釈するとサンプルコードの挙動は以下のようになる。

  1. task_one()が実行され、await asyncio.sleep(0.1)によりコルーチンであるasyncio.sleep()内部が実行される
  2. asyncio.sleep()内部のawait futureで制御を手放す
  3. task_two()が開始されawait time_sleep()により、コルーチンであるtime_sleep()`内部が実行される
  4. time_sleep()内には制御を手放す構文はないためtime.sleep(5)ごとそのまま実行され呼び出し元に返る