trioによる並行処理④（Nursery）

[目次](https://qiita.com/Kyosuke_Ichikawa/items/b7fdffcbfe00773074a6)

[trio](https://trio.readthedocs.io/en/stable/index.html)の[Nursery](https://trio.readthedocs.io/en/stable/reference-core.html?highlight=Nursery#trio.Nursery)をもう少し深掘りします。
余談ですがNurseryは保育園とか苗床という意味のようです（[参考](https://ejje.weblio.jp/content/nursery)）。
保育園で子供（子タスク）をどんどん走らせるイメージでいると中々可愛いですね。

## Nursery.start_soon()
[trioによる並行処理①](https://qiita.com/Kyosuke_Ichikawa/items/60955d36fa4c181c198e)の記事で紹介したように、[start_soon()](https://trio.readthedocs.io/en/stable/reference-core.html?highlight=Nursery#trio.Nursery.start_soon)メソッドを使用することで子タスクを実行できます。

```python
import trio

async def add(a, b):
    print("add start")
    await trio.sleep(1)
    print(f"add done: {a} + {b} = {a + b}")

async def sub(a, b):
    print("sub start")
    await trio.sleep(1)
    print(f"sub done: {a} - {b} = {a - b}")

async def main():
    async with trio.open_nursery() as nursery:
        nursery.start_soon(add, 3, 2)
        nursery.start_soon(sub, 1, 3)
        print("end of nursery")
    print("all done")

trio.run(main)
```
```:実行結果例
end of nursery
add start
sub start
add done: 3 + 2 = 5
sub done: 1 - 3 = -2
all done
```
```start_soon()```メソッドを使った場合、子タスクの開始を待たずにその下の処理が実行されます。
そのため```end of nursery```が最初に出力されます。
尚、```start_soon```はデコレータとして使うこともできます（関数の上に```@nursery.start_soon```を付ける）。
例えば下記は先ほどの結果と同じになります。

```python
import trio

async def main():
    async with trio.open_nursery() as nursery:
        a = 3
        b = 2
        @nursery.start_soon
        async def add():
            print("add start")
            await trio.sleep(1)
            print(f"add done: {a} + {b} = {a + b}")

        c = 1
        d = 3
        @nursery.start_soon
        async def sub():
            print("sub start")
            await trio.sleep(1)
            print(f"sub done: {c} - {d} = {c - d}")

        print("end of nursery")
    print("all done")

trio.run(main)
```

## Nursery.cancel_scope.cancel()

```start_soon()```と同様に子タスクを走らせる[start()](https://trio.readthedocs.io/en/stable/reference-core.html?highlight=Nursery#trio.Nursery.start)メソッドがあります。
- ```start_soon()```は同期関数ですが、```start()```は非同期関数です。
  そのため```await start(非同期関数)```のように呼ぶ必要があります。
- ```start()```に渡す非同期関数は必ず```task_status```というキーワードの引数が必要であり、関数内で```task_status.started()```を呼ぶ必要があります。その非同期関数は次のように定義します。
   ```python
    async def 非同期関数(引数1, 引数2, *, task_status=trio.TASK_STATUS_IGNORED):
        ...
       task_status.started()
       ...
   ```
   ここで```trio.TASK_STATUS_IGNORED```はグローバル変数であり、上記の通り```started()```というメソッドを持ちます。
- ```start_soon()```と違い```start()```を使った場合はその下の処理がすぐに実行されないです。
  しかし、```task_status.started()```が呼ばれると```start()```の下の処理が実行されます。

```python:サンプル
import trio

async def add(a, b, *, task_status=trio.TASK_STATUS_IGNORED):
    print("add start")
    await trio.sleep(1)
    print("task_status.started() start")
    task_status.started()
    print("task_status.started() done")
    await trio.sleep(1)
    print(f"add done: {a} + {b} = {a + b}")

async def sub(a, b):
    print("sub start")
    await trio.sleep(2)
    print(f"sub done: {a} - {b} = {a - b}")

async def main():
    async with trio.open_nursery() as nursery:
        await nursery.start(add, 3, 2)
        nursery.start_soon(sub, 1, 3)
        print("end of nursery")
    print("all done")

trio.run(main)
```
```:実行結果例
add start
task_status.started() start
task_status.started() done
end of nursery
sub start
sub done: 1 - 3 = -2
add done: 3 + 2 = 5
all done
```
このように
- ```start()```の下の処理がすぐには実行されないため、"end of nursery"よりも先に"add start"が出力されます
- ```task_status.started()```が呼ばれたタイミングで```start()```の下の処理が進み、"end of nursery"が出力されます
  - 厳密には非同期な```await trio.sleep(1)```が呼ばれたタイミングで```start()```の下の処理が進むと思われます（そのため、"end of nursery"よりも先に"task_status.started() done"が出力されています）
  
尚、```task_status.started()```には値を渡すことができ、それが```start()```の返り値となります。
```python:サンプル
import trio

async def add(a, b, *, task_status=trio.TASK_STATUS_IGNORED):
    print("add start")
    await trio.sleep(1)
    result = a + b
    task_status.started(result)
    print(f"add done: {a} + {b} = {result}")

async def main():
    async with trio.open_nursery() as nursery:
        result = await nursery.start(add, 3, 2)
        print(f"end of nursery (result={result})")
    print("all done")

trio.run(main)
```
```:実行結果
add start
add done: 3 + 2 = 5
end of nursery (result=5)
all done
```
## Nursery.start_soon()
[trioによる並行処理①](https://qiita.com/Kyosuke_Ichikawa/items/60955d36fa4c181c198e)の記事で紹介したように、[start_soon()](https://trio.readthedocs.io/en/stable/reference-core.html?highlight=Nursery#trio.Nursery.start_soon)メソッドを使用することで子タスクを実行できます。

```python
import trio

async def add(a, b):
    print("add start")
    await trio.sleep(1)
    print(f"add done: {a} + {b} = {a + b}")

async def sub(a, b):
    print("sub start")
    await trio.sleep(1)
    print(f"sub done: {a} - {b} = {a - b}")

async def main():
    async with trio.open_nursery() as nursery:
        nursery.start_soon(add, 3, 2)
        nursery.start_soon(sub, 1, 3)
        print("end of nursery")
    print("all done")

trio.run(main)
```
```:実行結果例
end of nursery
add start
sub start
add done: 3 + 2 = 5
sub done: 1 - 3 = -2
all done
```