Kotlin Coroutine のキャンセル周りの話②

Kotlin Coroutine について勉強したことのまとめ第二弾。

第一弾はこちら　
[Kotlin Coroutine の基本とキャンセルの伝播の話 -launchで伝播する流れを実際に読んでみた-](https://qiita.com/kkkkan/items/2b32f011236d64acdcb9)

------
# 環境/前提
- Coroutinesのバージョンはv1.5.2
- Kotlinのバージョンはv1.5.31

挙動を試すときは、[公式のonline Kotlin playground](https://play.kotlinlang.org/) を使用すると便利です。(`import`文を書けば)coroutineも使用できます。

------

# `async`と`withContext`のキャンセルの伝播の仕様

------
[第一弾でlaunchのコードを追ったとき](https://qiita.com/kkkkan/items/2b32f011236d64acdcb9#launch%E3%81%AE%E5%86%85%E9%83%A8%E5%AE%9F%E8%A3%85%E3%82%92%E8%A6%8B%E3%81%A6%E3%81%BF%E3%82%8B)のように、`async`と`withContext`も軽く内部実装やコメントを見つつメソッドの仕様やキャンセルの伝播の仕様を紹介します。

-----

## `aync`
[CorouitneScope#async](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/Builders.common.kt#L82)

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/360fb398-0da7-8ab7-5922-bd761bcf3379.png)|
|:--|


------

[launchを追ったときの教訓](https://qiita.com/kkkkan/items/2b32f011236d64acdcb9#-launch%E3%81%AE%E5%AE%9F%E8%A3%85%E3%81%AB%E6%88%BB%E3%82%8B)よりデフォルトは`start.isLazy`は`false`です。
したがって、`DeferredCoroutine`の実装を見てみます。

[DeferredCoroutine](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/Builders.common.kt#L96)

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/e8a9b4b0-0657-d2a9-95e8-784275a92b6d.png)|
|:--|

------

`DeferredCoroutine`は[launchを追ったときにも出てきた](https://qiita.com/kkkkan/items/2b32f011236d64acdcb9#-abstractcoroutine-%E3%81%AE%E5%AE%9F%E8%A3%85)`AbstractCoroutine`の子クラスのようです。
また、その他`async`の内部実装に使われている`newCoroutineContext`メソッドや`AbstaractCoroutine#start`を呼ぶ構造も`launch`と一緒です。

よって、[launchを追ったときの結果](https://qiita.com/kkkkan/items/2b32f011236d64acdcb9#%E3%81%BE%E3%81%A8%E3%82%81)から、
**`async`に引数として渡されたラムダ関数の中で例外が発生した場合は,**
**`async`呼び出し時に渡された`CoroutineContext`の`Job`要素または`async`を起動した`CoroutineScope`に紐づいた`CoroutineContext`の`Job`**
**を親にした`CoroutineScope`かつ`Job`である`DeferredCoroutine`**
が**例外を受け取ります。**


------

※ちなみに、`start.isLazy`が`true`の時に使われている`LazyDeferredCoroutine`は、`DeferredCoroutine`の子クラスです。

[LazyDeferredCoroutine](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/Builders.common.kt#L107)

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/f4a729a5-5441-dd43-5e60-6a24a9e728ad.png)|
|:--|

------

`async`のコード例と実行結果です。

【例】

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/c2cb66a0-87fa-ddde-d931-7dce7628ba7f.png)|
|:--|

https://pl.kotl.in/rGxttmY_v?theme=darcula

-------

【実行結果 出力】

```
2
1
catch: java.lang.Exception: error
Exception in thread "DefaultDispatcher-worker-2 @coroutine#1" java.lang.Exception: error
	at FileKt$main$1$1$1.invokeSuspend(File.kt:14)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
```

`coroutine 3`で発生した例外は`corouitne 3`に紐づいた'Job'である`DeferredCoroutine`が受け取り、`Job`の親子関係に沿って`corouitne 4`の`Job`まで伝わりキャンセルされるので、`println("3")`は実行されません。


--------


ちなみに、`async`に渡したラムダの中で発生した例外は`async#await()`を`try-catch`で囲むと捕捉できます(先ほどのコード例`println(catch: $e)`で実行結果の`catch: java.lang.Exception: error`が出力される部分)が、それでも`coroutine`のキャンセルの伝播が止められるわけではありません。
それは、エラーの伝播は「そのコルーチンに紐づいた`Job`へ例外が到達したか」に依存するのに対して、`async`の中で例外が発生した場合に`async#await()`はその例外を`throw`する仕様で`async#await()`を囲んだ`try-catch`でキャッチできているのはその`await`が投げた例外だからです。

--------

なので、以下のように`await`を呼ばないと`catch`節は呼ばれないし、

【例】

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/437edc5e-a8bb-1b24-d9f8-53d00b9a67e5.png)|
|:--|


https://pl.kotl.in/QCowVYgvV?theme=darcula

---------

【実行結果 出力】

```
2
1
Exception in thread "DefaultDispatcher-worker-1 @coroutine#4" java.lang.Exception: error
	at FileKt$main$1$1$1.invokeSuspend(File.kt:14)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
```

----

離れたところで`await`を呼びそれを`try-catch`で囲んでも`catch`節で例外を捕捉できます。そしてキャンセルの伝播自体は起きます。

【例】

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/8c5892d3-d23d-8634-fa9a-b80582fc7204.png)|
|:--|

https://pl.kotl.in/8lRZhUtRF?theme=darcula

-------

【実行結果 出力】

```
2
1
catch: java.lang.Exception: error
Exception in thread "DefaultDispatcher-worker-2 @coroutine#1" java.lang.Exception: error
	at FileKt$main$1$async1$1$1.invokeSuspend(File.kt:14)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
```

## withContext

[withContext](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/Builders.common.kt#L141)

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/66c9f3cb-160a-291b-7c3a-a31aa98483f3.png)|
|:--|

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/c27e1e72-0d0f-71b1-55a7-d70c1a4e3c26.png)|
|:--|

※長いのでスクリーンショットがコメント部と実装部の2枚になりました。

----------

① … 現在の`CoroutineContext`に、引数で渡された`CoroutineContext`のインスタンスを加算しています。(順番大事)

----------

② … [ScopeCoroutine](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/internal/Scopes.kt#L15)

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/9e2bc5af-b9ce-6734-aaa5-64d19156c902.png)|
|:--|

こちらも`AbstractCoroutine`の子クラスのようです。

**ただし、`launch`や`async`との違いとして、赤く囲われた部分が重大なポイントになっています。**

--------

### final override val isScopedCoroutine : Boolean get() = true とは?

-------


`override`している元はどこ? 
→ [`JobSupport`で定義されています。](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/JobSupport.kt#L1006)
※[`AbstractCoroutine`は`JobSupport`を継承しています。](https://qiita.com/kkkkan/items/2b32f011236d64acdcb9#-abstractcoroutine-%E3%81%AE%E5%AE%9F%E8%A3%85)


|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/bed89cc2-7ef9-8610-8b22-7631c51906d8.png)|
|--|

→ **`isScopedCoroutine`が`true`になるときは、例外をそれより内側のスコープにしか投げない、`scoped coroutine`と呼ばれるものになる!!**
- 自分の子や孫で発生したエラーは親には伝播させません。
- 自分の親から伝播してきたキャンセルは、子や孫に伝播させます。


---------
③ … [UndispatchedCoroutine](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/Builders.common.kt#L209)

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/d0e7cc95-070b-e22e-52fd-1988b138cd5e.png)|
|:--|

こちらは`ScopeCoroutine`の子クラス=`AbstractCoroutine`の子クラスのようです。

---------
④ … [DispatchedCoroutine](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/Builders.common.kt#L219)

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/2577203a-79d1-23a4-f9b3-04f4340ba708.png)|
|:--|

こちらも`ScopeCoroutine`の子クラス=`AbstractCoroutine`の子クラスのようです。

------

- `withContext`は`CoroutineScope`のメソッドではありません。
- `launch`などと同様`suspend fun`なので`CoroutineScope`の中でしか呼べません。
- 【`current corouitneContext` + 引き数で渡された`croutineContext`】(順番大事)として作った`CoroutineContext`をもつ`CoroutineScope`で引数として渡されたラムダ関数を実行します。
- ラムダ関数が終わるまで待って、結果を返します。
- **ラムダ関数の中で例外が発生した場合、親のスコープには例外が伝播しません。**

------

【例】

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/e4c03cfd-b7e5-939d-37bc-f56bf6d9733e.png)|
|--|

https://pl.kotl.in/jiM1F2hfl?theme=darcula

-----


【実行結果 出力】


```
1
catch: java.lang.Exception: error
2
```

----

- 一つ目の`launch`と、`withContext`のスコープに紐づいた`CoroutineContext`の`Job`には親子関係があり、またひとつめの`launch`とふたつめの`launch`を起動している`CoroutineScope`に紐づいた`CoroutineContext`は同じものですが、エラーは伝播していません。
- エラーの伝播はしませんが、`withContext`から中でキャッチされなかった例外は外に投げられています。


------

# `SupervisorJob`

------

[SupervisorJob(parent: Job? = null)](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/Supervisor.kt#L33)


|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/46988db5-1a50-76a9-af66-adfe241e6ecc.png)|
|--|

※[SupervisorJobImpl(parent: Job?)](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/Supervisor.kt#L65)


|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/5a48020b-fd57-9664-37d9-a8c7786fa861.png)|
|--|



--------------

- 1つの子で例外が発生したとしても他の子にキャンセルを伝播させたくない！という時に使える`Job`
例 : WebAPIをたたいたレスポンスデータを表示させるリストと、ローカルDBのデータを表示するリストを持っている画面で、どっちかの取得時に例外が発生したとしてももう片方までキャンセルする必要はない。

- 子Jobがエラーしても他の子Jobにキャンセルを伝播させないJob

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/abfa6b57-ef04-9b81-a6ac-429aebebcc8b.png)|
|--|

------------

- 引数にJobを渡すとそのJobの子Jobになる。
- 伝播してきたキャンセルは自分の子に伝播する。

※この辺りは普通のJobと同じ

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/df0eceda-021f-bda9-85e3-2181054bd2e8.png)|
|--|


--------

- SupervisorJobの子Jobは(普通にlaunchなどを使う場合は)SupervisorJobではない(孫Job同士はキャンセルが伝播する)ことに注意

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/fe9b7f95-4813-d0df-d284-8ed9fb69b1a8.png)|
|--|


-------

【例】
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/69509369-ced4-6c49-d45a-3c9bd79a7650.png)|
|--|

https://pl.kotl.in/Q8kJ0NTY-?theme=darcula

------

【実行結果 】出力】


```
1
3
5
Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.RuntimeException: exception
	at FileKt$main$2$2.invokeSuspend(File.kt:23)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
2
````

-----

【解説/捕捉】

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/3ac4fe26-d6ca-29f2-0bff-4e1352de26b5.png)|
|--|

① … ラムダが実行されるのは`SupervisoJob`の子に紐づいたスコープ。キャンセルされない。
② … ラムダが実行されるのは`SupervisoJob`の子に紐づいたスコープ。自分の子からエラーが伝播してきたエラーを受け取る。
③ … ラムダが実行されるのは`SupervisoJob`の孫に紐づいたスコープ。片方で発生したエラーにより、もう片方もキャンセルする。(伝播している。)

------

# `CoroutineExceptionHandler`

------


[CoroutineExceptionHandler](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt#L98)

- CoroutineContextのひとつ。
- キャッチされなかった例外を処理できる(ことがある)。(詳しくは後述)


|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/3f05c3b9-b832-6845-fea6-d5cf1a522932.png)|
|--|



-------

【例】

**キャッチできなかった例外を処理できるとき**

--------

これが

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/c2b0963e-afda-54ea-cf0b-a30e9b6878a0.png)|
|--|

https://pl.kotl.in/JidV950to?theme=darcula

【実行結果】


```
1
Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.RuntimeException: テスト
	at FileKt$main$2.invokeSuspend(File.kt:16)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
```


---------

こうなる　

- エラーが伝播しないとかではない。
- 伝播させた後の処理についてカスタマイズできるだけ。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/4a7912a2-99b3-9930-7f2d-d5ae334ccc59.png)|
|--|


https://pl.kotl.in/xPtg1J_9K?theme=darcula


【実行結果】

```
1
例外キャッチ : java.lang.RuntimeException: テスト
```

---------

※CoroutineExceptionHandlerを設定できるのは
コルーチンスコープそのものの作成時かルートのみ

 = 途中で上書きしようとしても意味をなさない

--------

【例】

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/da5dbc70-5c24-2d27-1f94-07e8ff87ef05.png)|
|--|

https://pl.kotl.in/Ktqe5aD3Z?theme=darcula

【実行結果 出力】


```
1
例外キャッチ2 : java.lang.RuntimeException: テスト
```


## キャッチされなかったエラーを処理できる「ことがある」とは？

------

`launch`のなかで発生したエラーはハンドリングできるけど、`async`のなかのものはハンドリングできません。

-----

理由をみていきます。

-----

### そもそも、どうやって`launch`はキャッチされない例外があった時に`CoroutineExceptionHandler`に処理を渡しているのか


------

`launch`で使われている`CoroutineContext`である[StandaloneCoroutine](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/Builders.common.kt#L187)
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/eda24b2b-78f1-c46d-365e-c6daea2ee475.png)|
|--|

赤枠の`handleJobException`メソッドのoverrideがポイント


-------

overrrride元 : [JobSupport#handleJobException](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/JobSupport.kt#L1027)

- 「最後まで処理されなかったExceptionを処理します。処理した時はtrueを返します。」


|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/049a4ea5-98b8-d5f8-a690-4401d823991d.png)|
|--|


------

- では、実際に処理している`handleCoroutineException`メソッドは何をしている?

[handleCoroutineException](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt#L21)


|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/752d231e-1d23-c3b9-a212-3af890bed680.png)|
|--|


① … `CoroutineContext`が`CoroutineExceptionHandler`要素を持つ場合は、それを呼び出して処理を委任
② … ない時や、`CoroutineExceptionHandler`が例外を投げた時はグローバルな(デフォルトの)ハンドラーに処理を委任


------

→ **`CoroutineExceptionHandler`がキャッチされなかった例外をハンドリングできるか否かは**
起動された`coroutine`の`JobSupport#handleJobException`の内容に左右される=**`coroutine`を起動するメソッドの実装に左右される**



------

- では、`aync`の場合

`async`で使用されている[DefferedCoroutine](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/Builders.common.kt#L96)

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/4c8648e1-fbe0-42e3-a46f-da60f163ac91.png)|
|--|

独自に`JobSupport#handleJobException`のoverrideはしていないようです。

-------

※`DefferedCoroutine‘の親クラスである`AbstractCoroutine`も[`JobSupport#handleJobException`はoverrideしていません。](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/AbstractCoroutine.kt#L37)

-----

→
したがって、`async`は`CoroutineExceptionHandler`要素を持つ`CoroutineContext`に紐づいた`CoroutineScope`で起動しても、`CoroutineExceptionHandler`でキャッチされなかった例外はハンドリングできません!

-----

# コルーチンビルダー

-----

## コルーチンビルダーとは

-----


新しい`coroutine`を起動するメソッド
  例 : `launch`、`async`など

[https://kotlinlang.org/docs/coroutines-basics.html#your-first-coroutine](https://kotlinlang.org/docs/coroutines-basics.html#your-first-coroutine)


- ここでは、特殊なコルーチンビルダーである`runBlocking`を紹介します。

------

## `runBlocking`

------

[runBlocking](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/jvm/src/Builders.kt#L38)

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/6da052cf-b9a4-46ac-f35f-c86111b62e02.png)|
|--|

------

- `CoroutineScope`のメソッドではない
- `suspend function`でもない
- `CoroutineScope`の中で呼んではいけない
- 呼び出し側のスレッドを止めて、渡されたラムダ関数を実行します
- ラムダ関数は`CoroutineScope`で実行されます
- `launch`で起動したものなど、子コルーチンのすべての終了を待つ
- ラムダ関数内でキャッチされない例外が発生した場合、`runBlocking`を呼び出したスレッドに例外が伝播する(ようだ)

------

【例】

- 何もしなくても、起動されたコルーチンが終わるまで待つ

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/649e01e9-696d-5e43-59df-3ca4d0204bfb.png)|
|--|

https://pl.kotl.in/R6DjyqSug?theme=darcula

-----

【実行結果 表示】


```
1
2
```


----


【例】
- 元Threadを止めます
- 値を返します

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/d76feb3f-e6be-6fe2-34c3-be677435707b.png)|
|--|

https://pl.kotl.in/gNeTXjB2S?theme=darcula

------

【実行結果 表示】


```
1
2
3
```

-------

【例】

- キャッチされない例外が発生した場合、呼び出し元スレッドに例外を投げます。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/d7cbe0df-515b-21b8-8f7b-70036b2d4f6a.png)|
|--|

https://pl.kotl.in/qSL6ZPWZj?theme=darcula

-----

【実行結果 表示】


```
1
Exception in thread "main" java.lang.RuntimeException: exception
 at FileKt$main$blocking$1.invokeSuspend (File.kt:10) 
 at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33) 
 at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106) 

```


-----

【例】

- キャッチされない例外が発生した場合、呼び出し元のスレッド(だけ)に例外を投げます、


|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/e00cba81-13a7-0ccc-4138-76712e870e64.png)|
|--|

https://pl.kotl.in/qInXCepLt?theme=darcula

------

【実行結果 表示】

```
4
1
Exception in thread "Thread-0" java.lang.RuntimeException: exception
	at FileKt$main$1$blocking$1.invokeSuspend(File.kt:11)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
5
```

------

# スコープビルダー

----

## スコープビルダーとは

-----

- 新しい`CoroutineScope`を作成するメソッド

[https://kotlinlang.org/docs/coroutines-basics.html#scope-builder](https://kotlinlang.org/docs/coroutines-basics.html#scope-builder)

- ここでは2つ(`coroutineScope`、`supervisorScope`)を紹介します。

------

## couitneScope

------

[coroutineScope](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/CoroutineScope.kt#L258)

- 挙動のほとんどは(`withContext`の時にも出てきた)`ScopeCoroutine`に依存しています。→[ScopeCoroutine](##withContext)

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/d7ddac49-2570-6d6f-1fd3-c08583e429e4.png)|
|--|

------

- `CoroutineScope`のメソッドではない
- `suspend fun` なので`CoroutineScope`のなかでしか呼べません
- 全ての子コルーチンの終了を待ちます
- 作成する`CoroutineScope`に紐づく`CoroutineContext`は、`current corouitneContext`を引き継ぐが`Job`だけは必ず上書きします
- しかしその`Job`は`current corouitneContext`の`Job`を親とします
-→
- `ScopeCoroutine`を使用しているので、**子や孫から伝播してきたエラーは親には伝播させません**
- **子でエラーが発生した場合、ほかの子はキャンセルします**


-----

【例】

- 全ての子コルーチンの終了を待ちます

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/fd5cf522-c711-ddc4-2771-f0a915753bf2.png)|
|--|


https://pl.kotl.in/OhFYaJZeQ?theme=darcula


------
【実行結果 表示】

```
1
2
3
4
5
6
7
```

-------

【例】

- 子や孫から伝播してきたエラーは親には伝播させません
- 子でエラーが発生した場合、ほかの子はキャンセルします

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/0e5f512f-b4a7-eac2-c9fe-3e2fff9e5c06.png)|
|--|

https://pl.kotl.in/RAjqeK6cn?theme=darcula

① … 他の子でエラーが発生したので、`delay`の間にキャンセルします
② … `coroutineScope`の子でエラーが発生しても`coroutineScope`に紐づいた`CoroutineContext`の`Job`の親にエラーは伝播しないので、キャンセルされずに実行されます



----

【実行結果 表示】

```
1
8
2
3
4
5
catch: java.lang.RuntimeException: error
7
9
```


------

【例】

- `Job`を介して自分の親に例外を伝播させることはありませんが、キャッチされない例外が発生した場合`coroutineScope`の外に例外を投げることはします

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/654c2536-64fb-d529-c098-7f0957975e93.png)|
|--|

https://pl.kotl.in/WFmAUPNu5?theme=darcula

① … `coroutineScope`が外に例外を投げる=ラムダ内でエラーが発生しました
② … ①の`launch`は`coroutineScope`で失敗しているので、②は実行されません
② … 同じ`Job`に紐づいた`CoroutineScope`から起動した①がエラーで失敗したので、`delay`の間でキャンセルが伝播して最後の`println("9")`は実行されません


-----

【実行結果 表示】

```
1
2
8
3
4
5
Exception in thread "DefaultDispatcher-worker-2 @coroutine#1" java.lang.RuntimeException: error
	at FileKt$main$1$1$1.invokeSuspend(File.kt:15)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
```







## supervisorScope

-----

[supervisorScope](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/Supervisor.kt#L55)

- 挙動のほとんどは`SupervisorCoroutine`に依存しているようです。(赤枠の中)

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/693a21c3-9cb0-c6ea-43ad-3df6889770a0.png)|
|--|


------

[SupervisorCoroutine](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/Supervisor.kt#L69)

- ベースは`ScopeCoroutine`で、`childCancelled`だけoverrideされているようです。


|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/e7bcb482-e9be-eb52-c316-ceeb50a0ab9a.png)|
|--|


------

- `CoroutineScope`のメソッドではない
- `suspend fun` なので`CoroutineScope`のなかでしか呼べません
- 全ての子コルーチンの終了を待ちます
- 作成する`CoroutineScope`に紐づく`CoroutineContext`は、`current corouitneContext`を引き継ぐが`Job`だけは必ず上書きします
(※コメントには「`SupervisorJob`で上書きする」、とありますが、コードを見る限り`SupervisorJob`と同じように`childCancelled`をoverrideしている`Job`を使用指定はいるけど`SupervisorJob`そのものを利用しているようには見えない…?)
- しかしその`Job`は`current corouitneContext`の`Job`を親とします
- `ScopeCoroutine`を使用しているので、**子や孫から伝播してきたエラーは親には伝播させません**
- **子でエラーが発生した場合、ほかの子はキャンセルしません**

→ **`coroutineScope`の、上書きする`Job`を`SupervisorJob‘にしたバージョン**です。

------
【例】

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/ee87ec7a-bcb2-ffd3-e2ac-00a74f872809.png)|
|--|

https://pl.kotl.in/Na-qzGiWF?theme=darcula

① … 他の子でエラーが発生しても、キャンセルされません
② … `supervisorScope`の子でエラーが発生しても`supervisorScope`に紐づいた`CoroutineContext`の`Job`の親にエラーは伝播しないので、キャンセルされずに実行されます


---

【実行結果 表示】

```
1
2
8
3
4
5
Exception in thread "DefaultDispatcher-worker-1 @coroutine#3" java.lang.RuntimeException: error
	at FileKt$main$1$1$1.invokeSuspend(File.kt:16)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
6
7
9
```


-----

【例】

- キャッチされない例外が発生した場合も`supervisorScope`の外に例外を投げることもしないようです。なぜかは分からず…。
※なので、ひとつ前の例でも`try-catch`で`catch`できていません

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/e8641f4f-359e-d07d-d23f-ef1e8046b72e.png)|
|--|

https://pl.kotl.in/J7_Oe2EdF?theme=darcula


① … `supervisorScope`は外に例外を投げないので、エラーは発生していません
② … 正常に実行されます
② … 同じ`Job`に紐づいた`CoroutineScope`から起動した①はエラーなしで成功したので、最後の`println("9")`も実行されます


-------

【実行結果 表示】

```
1
2
8
3
4
5
Exception in thread "DefaultDispatcher-worker-1 @coroutine#3" java.lang.RuntimeException: error
	at FileKt$main$1$1$1.invokeSuspend(File.kt:15)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
6
7
9
```


---------

# コルーチンビルダー、スコープビルダーの「全ての子コルーチンの終了を待つ」について

-------


- 子コルーチンを起動する時に`CoroutineDispatcher`等を新しいものに上書きしても大丈夫だが、`Job`を新しいものにすると待ってくれなくなります。(`runBuilding`,`coroutineScope`,`supervisorScope`すべて)
- ドキュメントにもコード上にも根拠は見つけられなったが、親子間のエラーの伝搬などがすべて`Job`に依存していることを考えると納得できる挙動ではある。


------
【例】

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/279ea844-95c5-8307-6a7e-034a8d7d47c0.png)|
|--|

https://pl.kotl.in/GbwwlukjZ?theme=darcula

-----
【実行結果 表示】

```
1
3
2
4
```

-----
【例】

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245217/8fdd52ef-c03c-8a87-1eda-a70a4aadc899.png)|
|--|

https://pl.kotl.in/U4Fyf_Ase?theme=darcula

----

【実行結果 表示】

```
1
3
4
2
```




--------

# コルーチンビルダーとスコープビルダーの違いについて

--------

※コルーチンビルダーとスコープビルダーの違いは、混とんとしているように見えます。

- 公式サイトの内容や各メソッドのコメントを見ると、`launch`や`runBlocking`はコルーチンビルダー/`coroutineScope`や`supervisorScope`はスコープビルダーとして書かれているように思われますが`launch`のなかで作成している`AbstractCoroutine`も`CoroutineScope`を実装しています。

[https://kotlinlang.org/docs/coroutines-basics.html#your-first-coroutine](https://kotlinlang.org/docs/coroutines-basics.html#your-first-coroutine)
[https://kotlinlang.org/docs/coroutines-basics.html#scope-builder](https://kotlinlang.org/docs/coroutines-basics.html#scope-builder)

- `final override val isScopedCoroutine : Boolean get() = true`な`Job`や`CoroutineScope`を使用しているものをスコープビルダーと呼ぶというのでもなさそうです。

(`runBlocking`で使用している[BlockingCoroutine](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/jvm/src/Builders.kt#L62)も、`coroutineScope`や`supervisorScope`で使用している[ScopeCoroutine](https://github.com/Kotlin/kotlinx.coroutines/blob/1.5.2/kotlinx-coroutines-core/common/src/internal/Scopes.kt#L15)も`final override val isScopedCoroutine : Boolean get() = true`なので)


→結局、どこに着目して分類されているかに左右されている?



-----

# 参考資料

-----

[https://kotlinlang.org/docs/coroutines-basics.html#scope-builder-and-concurrency](https://kotlinlang.org/docs/coroutines-basics.html#scope-builder-and-concurrency)
[https://kotlinlang.org/docs/exception-handling.html#coroutineexceptionhandler](https://kotlinlang.org/docs/exception-handling.html#coroutineexceptionhandler)
[https://zenn.dev/at_sushi_at/books/edf63219adfc31](https://zenn.dev/at_sushi_at/books/edf63219adfc31)
[https://zenn.dev/wm3/articles/aa85d6cc7aa0a8146863#coroutine-builder-%E3%81%A8-coroutinescope](https://zenn.dev/wm3/articles/aa85d6cc7aa0a8146863#coroutine-builder-%E3%81%A8-coroutinescope)
[https://mahata.gitlab.io/post/2019-04-23-coroutines-kotlin/](https://mahata.gitlab.io/post/2019-04-23-coroutines-kotlin/)
[https://speakerdeck.com/ntaro/kotlin-contracts-number-m3kt?slide=12](https://speakerdeck.com/ntaro/kotlin-contracts-number-m3kt?slide=12)
その他記事内のリンクのページなど