1. maueki

    Posted

    maueki
Changes in title
+Python3.5 の async/await
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,121 @@
+
+C#においては`async`は内部に`await`を書けるという以外何もしないが(要出典)、pythonの`async def`は`def`とは機能が異なっている。
+
+```py:hello.py
+import asyncio
+
+async def hello_world():
+ print("Hello World!")
+
+hello_world()
+
+```
+
+```bash
+$ python3 hello.py
+hello.py:6: RuntimeWarning: coroutine 'hello_world' was never awaited
+ hello_world()
+```
+
+pythonにおいて`async def`は[コルーチン関数を定義するための構文](https://docs.python.org/ja/3/reference/compound_stmts.html#coroutine-function-definition)であり 、[コルーチンはイベントループ内でしか実行されない](https://docs.python.org/ja/3/library/asyncio-task.html#coroutines)。
+
+## `await future`と`await coroutine`
+
+[18.5.3.1. コルーチン](https://docs.python.org/ja/3/library/asyncio-task.html#coroutines)を読んでいると気になる記述に出くわす。
+
+> コルーチンができること:
+>
+> * `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の`await`は`future`と`coroutine`の2つが取れるらしいが、挙動が微妙に違う。
+`await future`の方はsuspends、つまり制御を手放すのに対して`await coroutine`の方は単にcoroutineの終了を待つだけに見える。
+
+ためしに以下のようなコードを書いてみる。
+
+```python: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()
+```
+
+```bash
+$ 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()`は[コルーチンである](https://docs.python.org/ja/3/library/asyncio-task.html#asyncio.sleep)。また、`time_sleep()`もコルーチンのはずである。片方は制御を手放し、片方は制御を手放さない。これはどういうことだろうか?
+
+ポイントは`asyncio.sleep()`の実装にある。
+
+```python: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)`ごとそのまま実行され呼び出し元に返る
+5.
+