今回はtrioのCancelScopeについて扱います。
CancelScope
下記のようにしてブロックを作ることができます。
with CancelScope() as cancel_scope:
...
メンバ | 内容 |
---|---|
deadline | タイムアウトの時間(動的に変更可能、コンストラクタでも設定可能) |
cancel() | 呼ぶとブロック内の処理をキャンセルしてブロックを抜ける |
cancelled_caught | このブロックがタイムアウトかキャンセルしたらTrue(ただし、実際にコードがキャンセルされていなければTrueにならない) |
cancel_called | このブロックがタイムアウトかキャンセルしたらTrue(実際にコードがキャンセルされていなくてもTrueになる) |
shield | Trueにするとこのブロック内ではブロック外のタイムアウトやキャンセルによって終了しない(動的に変更可能、コンストラクタでも設定可能) |
deadline, cancelled_caughtを使ってみる
import trio
async def main():
with trio.CancelScope() as cancel_scope:
now = trio.current_time()
print(f"now={now:.0f}, deadline={cancel_scope.deadline:.0f}")
cancel_scope.deadline = now + 2
print(f"now={now:.0f}, deadline={cancel_scope.deadline:.0f}")
while True:
await trio.sleep(1)
print(cancel_scope.cancelled_caught)
trio.run(main)
now=235232, deadline=inf
now=235232, deadline=235234
True
このように、deadline
のデフォルトはinfとなります(コンストラクタで指定可能)。
上の例の通り動的に変えることができます。
move_on_after(), move_on_at()との関係
CancelScope
はtrioによる並行処理③(move_on_after, move_on_at)の回でもしれっと出てきてました。
move_on_after()やmove_on_at()のwithブロックを作った時に帰ってくるオブジェクトがCancelScope
です。
import trio
async def main():
with trio.move_on_after(1) as cancel_scope:
print(f"now={trio.current_time():.0f}, deadline={cancel_scope.deadline:.0f}")
while True:
await trio.sleep(1)
print(cancel_scope.cancelled_caught)
trio.run(main)
now=174955, deadline=174956
True
move_on_after()
でタイムアウトを1秒後にしているため、deadlineは1秒後を指します。
cancel()を使ってみる
下記はタイムアウトではなくcancel()
により終了するサンプルです。
import trio
async def main():
with trio.CancelScope() as cancel_scope:
now = trio.current_time()
print(f"now={now:.0f}, deadline={cancel_scope.deadline:.0f}")
while True:
await trio.sleep(3)
cancel_scope.cancel()
print(cancel_scope.cancelled_caught)
print(f"elapsed_time={trio.current_time() - now:.1f}")
trio.run(main)
now=99488, deadline=inf
True
elapsed_time=3.0
deadline
がinfであるにも関わらず終了できています。cancelled_caught
もTrueになりました。
cancelled_caughtとcancel_calledの違い
cancelled_caught
とcancel_called
はどちらも「このブロックがタイムアウトかキャンセルしたらTrueになるフラグ」です。
import trio
async def main():
with trio.CancelScope() as cancel_scope:
cancel_scope.cancel()
await trio.sleep(1)
print(cancel_scope.cancelled_caught)
print(cancel_scope.cancel_called)
trio.run(main)
True
True
このように多くの場合はどちらも同じ結果になります。
しかし、実際にコードがキャンセルされなければcancelled_caught
はTrueになりません。
例えばcancel_scope.cancel()
とawait trio.sleep(1)
の順番を入れ替えると
import trio
async def main():
with trio.CancelScope() as cancel_scope:
await trio.sleep(1)
cancel_scope.cancel()
print(cancel_scope.cancelled_caught)
print(cancel_scope.cancel_called)
trio.run(main)
False
True
ここで、cancel()
はブロックの最後で呼ばれていて意味がないです(呼ばなくてもブロックを抜ける)。
コードをキャンセルしてるわけではないので、cancelled_caught
はFalseとなりました。
shieldを使ってみる
shieldをTrueにすると、このブロック内ではブロック外のタイムアウト・キャンセルによって終了しなくなります。
まず、下記コードはshieldを使わない例です。
import trio
async def main():
start_time = trio.current_time()
with trio.CancelScope() as cancel_scope1:
with trio.CancelScope() as cancel_scope2:
cancel_scope1.cancel()
await trio.sleep(3)
print(f"elapsed_time={trio.current_time() - start_time:.1f}")
print(f"cancel_scope1.cancelled_caught={cancel_scope1.cancelled_caught}")
print(f"cancel_scope2.cancelled_caught={cancel_scope2.cancelled_caught}")
trio.run(main)
elapsed_time=0.0
cancel_scope1.cancelled_caught=True
cancel_scope2.cancelled_caught=False
cancel_scope1.cancel()
でただちにブロックを抜けています。
そのため、elapsed_time
(開始からブロックを抜けるまでの時間)は0秒です。
2つ目のブロックでshieldをTrueにすると下記のようになります。
import trio
async def main():
start_time = trio.current_time()
with trio.CancelScope() as cancel_scope1:
with trio.CancelScope(shield=True) as cancel_scope2:
cancel_scope1.cancel()
await trio.sleep(3)
print(f"elapsed_time={trio.current_time() - start_time:.1f}")
print(f"cancel_scope1.cancelled_caught={cancel_scope1.cancelled_caught}")
print(f"cancel_scope2.cancelled_caught={cancel_scope2.cancelled_caught}")
trio.run(main)
elapsed_time=3.0
cancel_scope1.cancelled_caught=False
cancel_scope2.cancelled_caught=False
elapsed_time
(開始からブロックを抜けるまでの時間)が3秒になりました。
2つ目のブロックでcancel_scope1.cancel()
の影響を受けていない事が分かります。
ちなみに2つ目のブロック終了後にtrio.sleep(5)
を置くと、そちらはただちに終了します。
import trio
async def main():
start_time = trio.current_time()
with trio.CancelScope() as cancel_scope1:
with trio.CancelScope(shield=True) as cancel_scope2:
cancel_scope1.cancel()
await trio.sleep(3)
await trio.sleep(5)
print(f"elapsed_time={trio.current_time() - start_time:.1f}")
print(f"cancel_scope1.cancelled_caught={cancel_scope1.cancelled_caught}")
print(f"cancel_scope2.cancelled_caught={cancel_scope2.cancelled_caught}")
trio.run(main)
elapsed_time=3.0
cancel_scope1.cancelled_caught=True
cancel_scope2.cancelled_caught=False
elapsed_time
(開始からブロックを抜けるまでの時間)が3秒のままです。
よって、あくまでもshieldをTrueにした2つ目のブロックだけがキャンセルされていない事が分かります。