Coroutine:blockingとnon-blockingの違い

投稿日:  更新日:

コルーチン(Coroutine)は「非同期処理の手法」の1つです。

Kotlinが提供します。

特徴としてnon-blocking動作をサポートします。

このnon-blocking動作についてまとめます。

スポンサーリンク

delay( )とThread.sleep( )の違い

サンプル(A)(B)は「3⇒2⇒1⇒0」と1秒の間隔でカウントダウンを行うプログラムです。

1秒の間隔を作るために、1つは(A)コルーチンのdelay( )を使い、もう1つは(B)Thread.sleep( )を使っています。(B)は単にdelay( )をThread.sleep( )に置き換えたものです。

        val _txtCount = findViewById<TextView>(R.id.txtCount)
        val _btnStart = findViewById<Button>(R.id.btnStart)
        val _btnStop = findViewById<Button>(R.id.btnStop)
		
		var _job: Job? = null

        _btnStart.setOnClickListener {
            _job?.cancel()				// 起動が重複した時、前のタスクをキャンセル
            _job = GlobalScope.launch(Dispatchers.Main) {  // Mainスレッドを起動
                _txtCount.text = "3"
                delay(1000)				// 1秒の間隔を作る
                _txtCount.text = "2"
                delay(1000)				// 1秒の間隔を作る
                _txtCount.text = "1"
                delay(1000)				// 1秒の間隔を作る
                _txtCount.text = "0"
            }
        }
		
        _btnStop.setOnClickListener {
            _job?.cancel()
        }
        val _txtCount = findViewById<TextView>(R.id.txtCount)
        val _btnStart = findViewById<Button>(R.id.btnStart)
        val _btnStop = findViewById<Button>(R.id.btnStop)
		
		var _job: Job? = null

        _btnStart.setOnClickListener {
            _job?.cancel()				// 起動が重複した時、前のタスクをキャンセル
            _job = GlobalScope.launch(Dispatchers.Main) {  // Mainスレッドを起動
                _txtCount.text = "3"
                Thread.sleep(1000)		// 1秒の間隔を作る
                _txtCount.text = "2"
                Thread.sleep(1000)		// 1秒の間隔を作る
                _txtCount.text = "1"
                Thread.sleep(1000)		// 1秒の間隔を作る
                _txtCount.text = "0"
            }
        }
		
        _btnStop.setOnClickListener {
            _job?.cancel()
        }
【(A)の実行結果 】


STARTのクリックで実行:OK

【(B)の実行結果 】

Thread.sleep( )を使った例
STARTのクリックで変化なし:NG

(A)コルーチンのdelay( )を使った例は正常に動きますが、(B)Thread.sleep( )を使った例はSTARTボタンを押してもカウントダウンしません。

delay( )もThread.sleep( )も「1秒の間隔を作る」構文であることに、違いは無いのですが…

これはスレッドの扱われ方の違いから生じています。

スポンサーリンク

non-blocking動作のdelay( )

delay( )は実行後にスレッドを一時停止(suspend)します。

一時停止したスレッドはプールに返却されて、新たな処理へ割り当てられるまで待機します。処理の要求があれば、プールから取得したスレッドへ割り当てます。

つまり、スレッドは新たな処理の割り当てを拒否しません。

これをnon-blocking動作といいます。

delay( )の場合のシーケンス図

サンプル(A)は1秒の間隔を作る間に画面描画(新たな処理)が実行できるので、アプリは正常に動作します。

スポンサーリンク

blocking動作のThread.sleep( )

sleep( )は実行後にスレッドを休止(sleep)します。

休止したスレッドは「休止」という処理を続けています。稼働した状態と同じで、新たな処理が割り当てできません。

つまり、スレッドは新たな処理の割り当てを拒否します。

これをblocking動作といいます。

Thread.sleep( )の場合のシーケンス図

コルーチンを処理している間、スレッドはシステムが発行する画面描画の実行も拒否します。サンプル(B)は、これが原因で画面は更新されず、アプリは何も変化しない見た目になります。

スポンサーリンク

Susupend関数とは

non-blocking動作を含んでいる関数のことをSuspend関数と呼びます。

Suspend関数は関数名の前にsuspend修飾子を付ける決まりになっています。

delay( )はSuspend関数です。ですので、suspend修飾子が付いています。

public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don`t delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
    }
}

※詳細は「Susupend関数とその仕組み」を参照

スポンサーリンク

non-blocking動作≒「スレッド+コールバック」

コルーチンのSuspend関数が利用可能になる以前は、non-blocking動作を「スレッド+コールバック」で表現していました。

例えばdelay( )は次のようになります。

                _txtCount.text = "2"
                delay(1000)
                _txtCount.text = "1"
                            _txtCount.text = "2"
                            _thread = Thread {				// スレッド
                                try { Thread.sleep(1000) }
                                catch (e: InterruptedException) { return@Thread }
                                runOnUiThread {				// コールバック
                                    _txtCount.text = "1"
                                }
                            }.apply { start() }

「スレッド+コールバック」の欠点は、

(1)non-blocking動作毎にThreadのインスタンスを作成
    ⇒起動のオーバーヘッドが大きい
(2)コールバック後にThreadインスタンスは不要
    ⇒破棄される、GCまでメモリにゴミが残る

です。

コルーチンはスレッドをプールから再利用するので、上記の欠点がありません。よくコルーチンのメリットとして述べられている点です。

スポンサーリンク

関連記事:

近頃の携帯端末はクワッドコア(プロセッサが4つ)やオクタコア(プロセッサが8つ)が当たり前になりました。 サクサク動作するアプリを作るために、この恩恵を使わなければ損です。 となると、必然的に非同期処理(マルチスレッド)を使うことになります。 JavaのThreadクラス、Android APIのAsyncTaskクラスが代表的な手法です。 Kotlinは上記に加えて「コルーチン(Coroutine)」が使えるようになっています。 今回は、このコルーチンについて、まとめます。 ...
コルーチン(Coroutine)は「非同期処理の手法」の1つです。 Kotlinが提供します。 コルーチンの構成要素であるSuspend関数について、まとめます。 ...
コルーチン(Coroutine)は「非同期処理の手法」の1つです。 Kotlinが提供します。 コルーチンはビルダー(Builder)により開始されます。 ビルダーは3つの種類があり、その中の1つがlaunchです。 このlaunchビルダーについて、まとめます。 ...
コルーチン(Coroutine)は「非同期処理の手法」の1つです。 Kotlinが提供します。 コルーチンを開始するlaunchビルダーの仕組みについて、まとめます。 ※仕組みの解析は次のバージョンを対象に行っています。    Kotlin:Ver 1.6.10    org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9 ...
コルーチン(Coroutine)は「非同期処理の手法」の1つです。 Kotlinが提供します。 コルーチンはビルダー(Builder)により開始されます。 ビルダーは3つの種類があり、その中の1つがasyncです。 このasyncビルダーについて、まとめます。 ...
コルーチン(Coroutine)は「非同期処理の手法」の1つです。 Kotlinが提供します。 コルーチンを開始するasyncビルダーの仕組みについて、まとめます。 ※仕組みの解析は次のバージョンを対象に行っています。    Kotlin:Ver 1.6.10    org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9 ...
コルーチン(Coroutine)は「非同期処理の手法」の1つです。 Kotlinが提供します。 コルーチンはビルダー(Builder)により開始されます。 ビルダーは3つの種類があり、その中の1つがrunBlockingです。 このrunBlockingビルダーについて、まとめます。 ...
CoroutineContextはコルーチンで起動されるスレッドの属性を格納しています。 その中にコルーチンの名前を表現するName属性があります。 Name属性を出力する方法を紹介します。 ...
コルーチン(Coroutine)は「非同期処理プログラミングの手法」の1つです。 Kotlinが提供します。 withContextはCoroutineContextを切り替えてスレッドを起動するSuspend関数です。 このwithContextについて、まとめます。 ...
コルーチン間でメッセージ(データのこと)の送受信を行うことが出来ます。 これにより、処理の投げっぱなしになりがちな非同期処理と、連携を強化できます。 しかも、ProduceやFlowを使うと記述が簡素になり、プログラミングの容易さと読みやすさが向上して便利です。 今回は、この「メッセージの送受信」について、使い方をまとめました。 ※環境:Android Studio Flamingo | 2022.2.1    :org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4 ...
スポンサーリンク