LoginSignup
2
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-08-25

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

はじめに

WorkManager に enqueue した Work は何並列で実行されるのか? の Coroutine 編です。
androidx.work:work-runtime-ktx に含まれる CoroutineWorker で実装した Worker が何並列で実行されるのか見てみました。

androidx.work: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
の記事によると、

override val coroutineContext = Dispatchers.IO

を実装する Worker で override すれば、スレッドを切り替えられると書かれています。
(だが、 deprecated...)
コード中にコメントを見るに、 withContext を使って

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 の記事中の Use coroutines for main-safety には、
スクリーンショット 2019-08-25 21.31.10(2).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 の設定次第では並列度が変わる可能性もありますね。

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