CoroutineWorker は何並列で実行されるのか？

# CoroutineWorker は何並列で実行されるのか？

## はじめに
[WorkManager に enqueue した Work は何並列で実行されるのか？](https://qiita.com/tatsuhama/items/83af70d57a1e7b9c9a21) の Coroutine 編です。
`androidx.work:work-runtime-ktx` に含まれる [CoroutineWorker](https://developer.android.com/reference/kotlin/androidx/work/CoroutineWorker.html) で実装した Worker が何並列で実行されるのか見てみました。

[androidx.work:work-*:2.2.0](https://developer.android.com/jetpack/androidx/releases/work#2.2.0) 、 Pixel 3a API 29 Emulator で確認済みです。

## 結果
* デフォルト：4
* `Dispatchers.IO` だと 20並列（試した範囲では）

※今回は WorkManager の Worker の並列度に注目していますが、 Dispatchers の並列度に依存する結果になっています
**※用法用量を守って正しい Dispatchers を指定しましょう。** 

## 試したコード
```MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)

        fab.setOnClickListener {
            // 10コ同時に enqueue する
            val workManager = WorkManager.getInstance(this@MainActivity)
            val requests = (1..10).map { MyWorker.createRequest(it) }
            workManager.enqueue(requests)
        }
    }
}
```
```MyWorker.kt
class MyWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {

    override suspend fun doWork(): Result {
        Log.d("★", "param = ${inputData.param} on ${Thread.currentThread().name}")
        Thread.sleep(1000)
        return Result.success()
    }

    private val Data.param: Int get() = inputData.getInt(KEY_PARAM, 0)

    companion object {
        const val TAG = "MyWorker"
        private const val KEY_PARAM = "KEY_PARAM"

        fun createRequest(param: Int): WorkRequest =
            OneTimeWorkRequest.Builder(MyWorker::class.java)
                .setInputData(createData(param))
                .addTag(TAG)
                .build()

        private fun createData(param: Int): Data =
            Data.Builder()
                .putInt(KEY_PARAM, param)
                .build()
    }

}
```
この実行結果

```
D/★: param = 2 on DefaultDispatcher-worker-2
D/★: param = 1 on DefaultDispatcher-worker-1
D/★: param = 3 on DefaultDispatcher-worker-4
D/★: param = 4 on DefaultDispatcher-worker-3
（約1秒）
D/★: param = 5 on DefaultDispatcher-worker-2
D/★: param = 6 on DefaultDispatcher-worker-1
D/★: param = 7 on DefaultDispatcher-worker-4
D/★: param = 8 on DefaultDispatcher-worker-3
（約1秒）
D/★: param = 9 on DefaultDispatcher-worker-2
D/★: param = 10 on DefaultDispatcher-worker-1
```
4並列でした。
（ 手元の環境では、Coroutine ではない Worker よりも並列度が1つ多かったです。）
では、どの Dispatchers で実行されていたのでしょうか？

```CoroutineWorker.kt
/**
 * The coroutine context on which [doWork] will run. By default, this is [Dispatchers.Default].
 */
@Deprecated(message = "use withContext(...) inside doWork() instead.")
open val coroutineContext = Dispatchers.Default

@Suppress("DEPRECATION")
final override fun startWork(): ListenableFuture<Result> {

    val coroutineScope = CoroutineScope(coroutineContext + job)
    coroutineScope.launch {
        try {
            val result = doWork()
            future.set(result)
        } catch (t: Throwable) {
            future.setException(t)
        }
    }

    return future
}
```
というコードがあり、 `Dispatchers.Default` だということがわかります。

[Threading in CoroutineWorker](https://developer.android.com/topic/libraries/architecture/workmanager/advanced/coroutineworker)
の記事によると、

```kotlin
override val coroutineContext = Dispatchers.IO
```
を実装する Worker で override すれば、スレッドを切り替えられると書かれています。
（だが、 deprecated...）
コード中にコメントを見るに、 `withContext` を使って

```kotlin
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
    Thread.sleep(1000)
    Log.d("★", "param = ${inputData.param} on ${Thread.currentThread().name}")
    Result.success()
}
```
と Dispatchers を指定するのが良さそうです。
　
実行結果↓

```
D/★: param = 1 on DefaultDispatcher-worker-1
D/★: param = 2 on DefaultDispatcher-worker-2
D/★: param = 3 on DefaultDispatcher-worker-3
D/★: param = 4 on DefaultDispatcher-worker-5
D/★: param = 5 on DefaultDispatcher-worker-6
D/★: param = 6 on DefaultDispatcher-worker-8
D/★: param = 7 on DefaultDispatcher-worker-7
D/★: param = 8 on DefaultDispatcher-worker-4
D/★: param = 9 on DefaultDispatcher-worker-9
D/★: param = 10 on DefaultDispatcher-worker-10
（約1秒）
```
少なくとも 10 並列はありそう。
どっと数を上げてみたところ、

```
D/★: param = 1 on DefaultDispatcher-worker-1
D/★: param = 2 on DefaultDispatcher-worker-2
D/★: param = 3 on DefaultDispatcher-worker-3
D/★: param = 4 on DefaultDispatcher-worker-5
D/★: param = 5 on DefaultDispatcher-worker-6
D/★: param = 6 on DefaultDispatcher-worker-4
D/★: param = 7 on DefaultDispatcher-worker-7
D/★: param = 8 on DefaultDispatcher-worker-10
D/★: param = 9 on DefaultDispatcher-worker-8
D/★: param = 10 on DefaultDispatcher-worker-13
D/★: param = 11 on DefaultDispatcher-worker-12
D/★: param = 12 on DefaultDispatcher-worker-14
D/★: param = 13 on DefaultDispatcher-worker-15
D/★: param = 14 on DefaultDispatcher-worker-16
D/★: param = 15 on DefaultDispatcher-worker-11
D/★: param = 16 on DefaultDispatcher-worker-19
D/★: param = 17 on DefaultDispatcher-worker-17
D/★: param = 18 on DefaultDispatcher-worker-20
D/★: param = 19 on DefaultDispatcher-worker-21
D/★: param = 20 on DefaultDispatcher-worker-22
（約1秒）
D/★: param = 21 on DefaultDispatcher-worker-11
D/★: param = 22 on DefaultDispatcher-worker-10
D/★: param = 23 on DefaultDispatcher-worker-15
D/★: param = 24 on DefaultDispatcher-worker-12
D/★: param = 25 on DefaultDispatcher-worker-7
D/★: param = 26 on DefaultDispatcher-worker-14
D/★: param = 27 on DefaultDispatcher-worker-4
D/★: param = 28 on DefaultDispatcher-worker-21
D/★: param = 29 on DefaultDispatcher-worker-3
D/★: param = 30 on DefaultDispatcher-worker-17
...
```
となりました。
`Dispatchers.IO` だと20並列で実行できました。
詳細は追い切れていませんが、 CoroutineWorker ではない Worker のときは CPU 数に応じた並列度だったので、 **並列度は実行環境に依存する可能性が大いに考えられます。**
（並列度を決定する処理を見つけたら紹介します。）

とはいえ、20並列で CPU をがっつり使うような処理を行って良いような気がしません。。。

## どの Dispatchers を指定すれば良いのか？
ちゃんと方針が示されていました。
[Improve app performance with Kotlin coroutines](https://developer.android.com/kotlin/coroutines) の記事中の [Use coroutines for main-safety](https://developer.android.com/kotlin/coroutines#main-safety) には、
![スクリーンショット 2019-08-25 21.31.10（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9695/106b08d7-946f-3626-3b5e-859cced35e12.png)
との記載があるので、 **用法用量を守って正しい Context を指定しましょう。** 

## 余談
`Dispatchers.Default` を初期化する処理を追ってみると、

```Dispatchers.kt
public actual object Dispatchers {
    ...
    @JvmStatic
    public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
    ...
}
```


```CoroutineContext.kt
internal const val COROUTINES_SCHEDULER_PROPERTY_NAME = "kotlinx.coroutines.scheduler"

internal val useCoroutinesScheduler = systemProp(COROUTINES_SCHEDULER_PROPERTY_NAME).let { value ->
    when (value) {
        null, "", "on" -> true
        "off" -> false
        else -> error("System property '$COROUTINES_SCHEDULER_PROPERTY_NAME' has unrecognized value '$value'")
    }
}

internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
    if (useCoroutinesScheduler) DefaultScheduler else CommonPool
```
というコードがあったので、 SystemProperty の設定次第では並列度が変わる可能性もありますね。