Pythonのasyncioとcontextmanagerの使い方メモ
Pythonでは、
asyncioを使うと非同期タスクの処理を書くことができて、
以下のページにわかりやすいサンプルもあります。
スレッドまたはプロセスプールでコードを実行する | docs.python.org
https://docs.python.org/ja/3/library/asyncio-eventloop.html#executing-code-in-thread-or-process-pools
ところで、
ファイルやネットワークI/Oをopenして処理するケースでは、
ContextManagerでwith句(非同期の場合はasync with)を使って
書くことができるとわかりやすいと思います。
調べ見ると、以下のページに、
contextlibにasynccontextmanagerというものがあったので、
使ってみたメモを残しておきます。
@contextlib.asynccontextmanager | docs.python.org
https://docs.python.org/ja/3/library/contextlib.html#contextlib.asynccontextmanager
同期タスクの場合
まずは、比較用に同期タスクの場合のコードです。
task_backgroundという関数を動かして、
I/O処理中にバックグラウンドの処理が行われるかを見えるようにしています。
study1.py
import asyncio
def blocking_io():
with open('/dev/urandom', 'rb') as f:
return f.read(100)
async def main():
async def task_background():
while True:
print("background task")
await asyncio.sleep(0)
task = asyncio.create_task(task_background())
print("begin")
result = blocking_io()
print("complete")
task.cancel()
if __name__=='__main__':
asyncio.run(main())
同期処理なので、バックグラウンドの処理は行われません。
$ python study1.py
begin
complete
非同期タスクの場合
次は、非同期タスクの場合です。
task_background関数を追加している以外は、
docs.python.orgのサンプルとほぼ同じです。
study2.py
import asyncio
def blocking_io():
with open('/dev/urandom', 'rb') as f:
return f.read(100)
async def main():
async def task_background():
while True:
print("background task")
await asyncio.sleep(0)
task = asyncio.create_task(task_background())
loop = asyncio.get_running_loop()
print("begin")
result = await loop.run_in_executor(None, blocking_io)
print("complete")
task.cancel()
if __name__=='__main__':
asyncio.run(main())
バックグラウンドの処理が動いていることが確認できます。
$ python study2.py
begin
background task
background task
background task
background task
background task
background task
background task
background task
background task
background task
background task
background task
complete
非同期タスクの場合(contextmanager版)
次は、contextmanagerを使った場合です。
async_funcという同期関数を非同期関数に置き換える関数を用意した上で、
async_openというcontextmanagerの中で、
openとreadを非同期関数に置き換えています。
python3.py
import asyncio
import functools
from typing import Callable
from contextlib import asynccontextmanager
def async_func(func) -> Callable:
async def f(*args, **kwargs):
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, functools.partial(func, *args, **kwargs))
return f
@asynccontextmanager
async def async_open(*args, **kwargs):
fp = await async_func(open)(*args, **kwargs)
fp.async_read = async_func(fp.read)
try:
yield fp
finally:
fp.close()
async def main():
async def task_background():
while True:
print("background task")
await asyncio.sleep(0)
task = asyncio.create_task(task_background())
loop = asyncio.get_running_loop()
print("begin")
async with async_open('/dev/urandom', 'rb') as f:
print("begin read")
result = await f.async_read(100)
print("complete read")
print("complete")
task.cancel()
if __name__=='__main__':
asyncio.run(main())
バックグラウンドの処理が動いていることが確認できます。
begin
〜begin read
で、openが非同期に実行されていることがわかり、
begin read
〜complete read
で、readが非同期に実行されていることがわかります。
$ python study3.py
begin
background task
background task
begin read
background task
background task
background task
background task
background task
background task
background task
background task
background task
background task
background task
background task
complete read
complete
標準ライブラリだけで、わりと楽に書けますね。
以上。