1. maueki

    No comment

    maueki
Changes in title
-Python3.5 の async/await
+Python3 の async/awaitを理解する
Changes in body
Source | HTML | Preview
@@ -1,124 +1,131 @@
-## ここは書きかけ
+# TL;DR
+`await coroutine`と`await future`の違いが理解できていればおおよそOK
-C#においては`async`は内部に`await`を書けるという以外何もしないが(要出典)、pythonの`async def`は`def`とは機能が異なっている。
+# はじめに
+
+## `async def`
+
+まずは`async`を使ってみる
```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)
+実行すると文句を言われてしまう。C#のasync/awaitに馴染んでる人だと「?」となるのではないだろうか。
+C#の場合は`async`がついていてもただの関数に過ぎないが、python`async def`は単なる関数定義ではなく[コルーチン関数定義](https://docs.python.org/ja/3/reference/compound_stmts.html#coroutine-function-definition)であり、実行するとコルーチンオブジェクトが返る。
+コルーチンオブジェクトはイベントループ内でのみ実行が可能であり、上記のようなコードを書くと警告が出てしまう
## `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)`ごとそのまま実行され呼び出し元に返る