Python の Version3.4 から始まった asyncio 周りについてのメモ。 環境は、Windows10 上の python3.6(64bit)。

Version Python3.4

asyncio yield from

Python3.5

async def await

EventLoop

https://docs.python.org/3/library/asyncio-eventloop.html

import asyncio
loop=asyncio.get_event_loop()
print(loop)
# <_WindowsSelectorEventLoop running=False closed=False debug=False>

loop.run_forever()
print('done')

ただし永遠(forever)走り続けるのでプロセスを kill しないと止まらず。 Windows 向けの loop

https://docs.python.org/3/library/asyncio-eventloops.html#asyncio.ProactorEventLoop

import asyncio
import sys

if sys.platform == 'win32':
    loop = asyncio.ProactorEventLoop()
    # <ProactorEventLoop running=False closed=False debug=False>
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()

以降、loop を得るコードを省略。 EventLoop に関数を投入する

def func(loop):
    loop.stop() # 停止

loop.call_soon(func, loop)
loop.run_forever()
print('done')

asyncio.get_event_loop で loop を取得。loop.call_soon で loop に関数を投入できる。 投入された関数は、loop.run_forever で消化される。 ついでに、loop.stop で run_forever から抜けることができる。 EventLoop に generator を投入する

def gen(loop, name, count):
    print(name, loop.time())
    for i in range(count):
        print(name, i, loop.time())
        yield
    print(name, 'done')
    loop.stop()

asyncio.ensure_future(gen(loop, 'a', 3))
asyncio.ensure_future(gen(loop, 'b', 3))
loop.run_forever()

a 534341.609
a 0 534341.609
b 534341.609
b 0 534341.609
a 1 534341.609
b 1 534341.609
a 2 534341.609
b 2 534341.609
a done
b done

loop.stop で止まった。 すべての task が終わるのを待つ

def gen(loop, name, count):
    print(name, loop.time())
    for i in range(count):
        print(name, i, loop.time())
        yield
    print(name, 'done')

taskA=asyncio.ensure_future(gen(loop, 'a', 3), loop=loop)
taskB=asyncio.ensure_future(gen(loop, 'b', 5), loop=loop)
future=asyncio.gather(taskA, taskB)
future.add_done_callback(lambda future: loop.stop())
task=asyncio.ensure_future(future, loop=loop)

loop.run_forever()

a 571911.359
a 0 571911.359
b 571911.359
b 0 571911.359
a 1 571911.359
b 1 571911.359
a 2 571911.359
b 2 571911.359
a done
b 3 571911.359
b 4 571911.359
b done

loop.run_until_complete で future が終わるのを待つ

def gen(loop, name, count):
    print(name, loop.time())
    for i in range(count):
        print(name, i, loop.time())
        yield
    print(name, 'done')
futureA=asyncio.ensure_future(gen(loop, 'a', 3), loop=loop)
futureB=asyncio.ensure_future(gen(loop, 'b', 5), loop=loop)
future=asyncio.gather(futureA, futureB)

loop.run_until_complete(future)

future の終了を待って loop.stop したいなら run_until_complete するのが明瞭。 PEP492 – Coroutines with async and await syntax(python3.5 09-Apr-2015)

generator を流用した coroutine は紛らわしいので、coroutine に独自のシンタックスを導入するで。native coroutine と呼称する。C で実装しているわけではない。 関数内で await を使わなくても coroutine として有効

async def read_data(db):
    pass

EventLoopにnative coroutineを投入する
async def gen(loop, name, count):
    print(name, loop.time())
    for i in range(count):
        print(name, i, loop.time())
        #yield
        await asyncio.sleep(0)
    print(name, 'done')

taskA=asyncio.ensure_future(gen(loop, 'a', 3), loop=loop)
taskB=asyncio.ensure_future(gen(loop, 'b', 5), loop=loop)
future=asyncio.gather(taskA, taskB)

loop.run_until_complete(future)

yield を await asyncio.sleep(0)で置き換えた。 yield のままだと TypeError になる。

Traceback (most recent call last):
  File ".\exp.py", line 18, in <module>
    taskA=asyncio.ensure_future(gen(loop, 'a', 3), loop=loop)
  File "D:\Python36\lib\asyncio\tasks.py", line 519, in ensure_future
    raise TypeError('A Future, a coroutine or an awaitable is required')
TypeError: A Future, a coroutine or an awaitable is required

It is a TypeError if __await__ returns anything but an iterator.

実験。

def it():
    yield

async def co_y():
    yield

async def co():
    pass

<class 'generator'>
<class 'async_generator'>
.\exp.py:22: RuntimeWarning: coroutine 'co' was never awaited
  print(type(co()))
<class 'coroutine'>

async_generator…。async def 内で yield すると違うものになるのね。syntax error にはできんな。 自前のメインループに loop を組み込むとすれば loop.once のような関数があると毎フレーム小出しにタスクを消化できるのだが。 ググってみた。

https://stackoverflow.com/questions/29868372/python-asyncio-run-event-loop-once

loop.stop(); loop.run_forever()

なるほど。

async def gen(loop, name, count):
    print(name, loop.time())
    for i in range(count):
        print(name, i, loop.time())
        await asyncio.sleep(0)
    print(name, 'done')

taskA=asyncio.ensure_future(gen(loop, 'a', 3), loop=loop)
taskB=asyncio.ensure_future(gen(loop, 'b', 5), loop=loop)
future=asyncio.gather(taskA, taskB)

count=1
while not future.done():
    print(count)
    count+=1
    # loop one step
    loop.stop()
    loop.run_forever()

print('done')

1
a 579281.828
a 0 579281.828
b 579281.828
b 0 579281.828
2
a 1 579281.828
b 1 579281.828
3
a 2 579281.828
b 2 579281.828
4
a done
b 3 579281.828
5
b 4 579281.828
6
b done
7
done

いいかんじになった。これなら付き合っていけそうだ。