Kotlinでのコルーチンのカプセル化とキャンセル

1.Javaのコールバックインターフェイスをサスペンド関数にカプセル化します

  • OKHttpリクエストを例にとると、コードは次のとおりです。
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Response
import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

suspend fun Call.await(): String = suspendCoroutine { block ->
    enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            block.resumeWithException(e)
        }

        override fun onResponse(call: Call, response: Response) {
            if (response.isSuccessful) {
                block.resume(response.body()!!.string())
            }
        }
    })
}

  • サスペンド機能の使用法は次のとおりです。
val client: OkHttpClient = OkHttpClient.Builder() .build()
        val request: Request = Request.Builder()
            .get()
            .url("http://xxx")
            .build()
  GlobalScope.async(Dispatchers.Main) {
            val call = client.newCall(request)
            val data = call.await()
        }

Callの拡張関数await()はサスペンド関数であるため、コルーチンまたは他のサスペンド関数で実行する必要があります

  • 引き続きOkHttpのCallクラスを拡張し、await()関数を追加します。関数の戻り値は文字列型です。関数でサスペンド関数suspendCoroutine {}またはsuspendCancellableCoroutine {}を使用します。await()関数が呼び出されると、最初に現在のコルーチンを一時停止し、次にenqueueを実行してネットワーク要求をキューに入れます。要求が成功したら、block.resume(response.body()!!。string())を使用して前のコルーチンを再開します。

  • suspendCancellableCoroutine {}関数の関数:

delay()関数

Yield()関数:現在のコルーチンを一時停止してから、コルーチンをDispatcherキューに配布します。これにより、コルーチンが配置されているスレッドまたはスレッドプールで他のコルーチンロジックを実行し、Dispatcherが次の場合に元のコルーチンを実行し続けることができます。アイドル。簡単に言えば、他のコルーチンに実行権を放棄することです。他のコルーチンが完了するか実行権を放棄すると、元のコルーチンは操作を再開できます。

2.父と息子のコルーチン

  • GlobalScope.launch()関数とGlobalScope.async()関数は、親コルーチンのないグローバルコルーチンと呼ばれます。

  • コルーチンが他のコルーチンによってCoroutineScopeで開始されると、CoroutineScope.coroutineContextを介してコンテキストが継承され、この新しいコルーチンのジョブは親コルーチンタスクの子タスクになります。親コルーチンがキャンセルされると、そのすべての子コルーチンも再帰的にキャンセルされます。例は次のとおりです。

GlobalScope.launch(Dispatchers.Main) {
            //父协程
            launch { 
                //子协程
            }
            async {
                //子协程 
            }
            withContext(coroutineContext){
                //子协程
            }
        }
  • コルーチン間の親子関係には、次の3つの効果があります。

    • 親コルーチンが手動でcancel()を呼び出すか、異常終了した場合、そのすべての子コルーチンはすぐにキャンセルされます。

    • 親コルーチンは、すべての子コルーチンが完了するまで(完了またはキャンセルされた状態で)完了するのを待つ必要があります。

    • 子コルーチンがキャッチされない例外をスローすると、その親コルーチンはデフォルトでキャンセルされます。

3.コルーチンのキャンセル

  • 例1:
fun main() = runBlocking {
    val job = launch {
        launch {
            repeat(6) {
                println("run_child:$it")
                delay(500)
            }
        }
        repeat(6) {
            println("run_parent:$it")
            delay(500)
        }
    }
    delay(1600)
    job.cancel()
    println("job end")

}
  • 結果

  • 上記のコードでジョブがキャンセルされた後、delay()はコルーチンがキャンセルされたかどうかを検出するため、job.cancel()呼び出しの後、上記の親コルーチンと子コルーチンもキャンセルされます。

  • 例2:

fun main() = runBlocking {
    val job = launch(Dispatchers.Default) {
        var nextTime = 0L
        var i = 1
        while (i <= 3) {
            val nowTime = System.currentTimeMillis()
            if (nowTime >= nextTime) {
                println("parent_${i++}")
                nextTime = nowTime + 500L
            }
        }
    }
    delay(800)
    job.cancel()
    println("job end")
}
  • 結果

  • 上記のコードでジョブがキャンセルされた後、コルーチンの状態を検出するロジックはなく、すべて計算ロジックであるため、ジョブの計算ロジックは引き続き実行されます。

  • コルーチンの操作ロジックを時間内にキャンセルするために、コルーチンの状態を検出し、isActiveを使用して判断することができます。上記の例では、while(i <= 3)をwhile(isActive)に置き換えることができます。 。コードは次のように表示されます。

fun main() = runBlocking {
    val job = launch(Dispatchers.Default) {
        var nextTime = 0L
        var i = 1
        while (isActive) {
            val nowTime = System.currentTimeMillis()
            if (nowTime >= nextTime) {
                println("parent_${i++}")
                nextTime = nowTime + 500L
            }
        }
    }

    delay(1200)
    job.cancel()
    println("job end")

}
  • 結果

  • キャンセルできないコードブロックを実行します。コルーチンを手動でキャンセルした後、delay()などのキャンセル可能な一時停止関数は、キャンセルされた状態を検出したときにCancellationExceptionをスローし、コルーチンを終了します。このとき、try {…} final {…}式または<T:Closeable?、R> T.use {}関数を使用して、最終アクションを実行したり、リソースを閉じたりできます。ただし、finallyブロックでカスタムまたはシステムのキャンセル可能なサスペンド関数を呼び出すと、CancellationExceptionが再度スローされます。通常、finallyブロックでファイルを閉じたり、タスクをキャンセルしたり、通信チャネルを閉じたりすることはブロックされておらず、サスペンド関数は呼び出されません。キャンセルされたコルーチンを一時停止する必要がある場合は、コードをwithContext(NonCancellable){…}でラップできます。

4.タイムアウトキャンセル

  • withTimeout(){…}一時停止関数がコルーチンライブラリに提供されており、タイムアウト後にコルーチンを自動的にキャンセルします。タイムアウト後にTimeoutCancellationExceptionをスローします。これはCancellationExceptionのサブクラスです。これがコルーチンの終了の通常の理由です。スタックトレース情報は出力されません。キャンセル後にリソースシャットダウン操作を実行する必要がある場合は、前述を試してください。{…}最後に{…}式。
try {
    withTimeout(1300L) {
        repeat(1000) { i ->
                println("I'm sleeping $i ...")
            delay(500L)
        }
    }
} finally {
    println("I'm running finally")
}
  • 例外をスローする代わりに、タイムアウト後にnullを返すwithTimeoutOrNull(){…}サスペンド関数もあります。
val result = withTimeoutOrNull(1300L) {
    repeat(1000) { i ->
            println("I'm sleeping $i ...")
        delay(500L)
    }
    "Done" /* 在它运行得到结果之前取消它,如果循环次数x延迟时间小等于1300则返回Done,否则返回null*/
}
println("Result is $result")

おすすめ

転載: blog.csdn.net/genmenu/article/details/87274409