try~finallyの罠
trioを使っている時、try~finallyの構文に注意が必要です。例えば
async def func():
try:
print("try start")
await trio.sleep(1)
print("try done")
finally:
print("finally start")
await trio.sleep(0)
print("finally done")
のような関数があるとき、finally内の全処理は常に実行されると思ってしまう方も居るのではないでしょうか。
しかし、下記サンプルが示すようにそうなりません。
サンプル
import trio
async def func():
try:
print("try start")
await trio.sleep(1)
print("try done")
finally:
print("finally start")
await trio.sleep(0)
print("finally done")
async def main():
with trio.move_on_after(0.5):
await func()
trio.run(main)
実行結果
try start
finally start
"finally done"が出力されていません。
つまり、move_on_after
やCancelScope
などのブロック内でtry~finallyを使った場合、タイムアウトやキャンセルで処理が中断していれば、finally内のawait 非同期関数()
以降の処理が実行されません。
これを認識していないとバグの原因になってしまいます。
回避策
前回紹介したCancelScope
のshield
によって回避できます。
サンプル
import trio
async def func():
try:
print("try start")
await trio.sleep(1)
print("try done")
finally:
with trio.CancelScope(shield=True):
print("finally start")
await trio.sleep(0)
print("finally done")
async def main():
with trio.move_on_after(0.5):
await func()
trio.run(main)
実行結果
try start
finally start
finally done
shieldをTrueにするとブロック外のタイムアウトやキャンセルによって終了しなくなるため、finally内の全処理を実行することができました。