LoginSignup
2
0

More than 1 year has passed since last update.

trioによる並行処理⑤(CancelScope)

Last updated at Posted at 2022-09-30

目次

今回はtrioCancelScopeについて扱います。

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()との関係

CancelScopetrioによる並行処理③(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_caughtcancel_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を使わない例です。

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にすると下記のようになります。

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つ目のブロックだけがキャンセルされていない事が分かります。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0