一手遮天 Android - kotlin 协程: Job 的等待与取消,超时处理,取消协程
示例如下:
/kotlin/coroutine/Demo2.kt
/**
* coroutine - 协程
* 本利用于演示 Job 的等待与取消,超时处理,取消协程
*
* 注:async 返回的是 Deferred<T> 对象,其继承自 Job,所以关于 Deferred<T> 的等待与取消和超时处理等与 Job 是一样的
*/
package com.webabcd.androiddemo.kotlin.coroutine
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.webabcd.androiddemo.R
import kotlinx.android.synthetic.main.activity_kotlin_coroutine_demo2.*
import kotlinx.coroutines.*
import java.text.SimpleDateFormat
import java.util.*
class Demo2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_kotlin_coroutine_demo2)
// 等待 Job 执行完
button1.setOnClickListener {
sample1()
}
// 取消 Job
button2.setOnClickListener {
sample2()
}
// 取消 Job 需要强调的知识点(声明为 suspend 的函数才可以检查取消)
button3.setOnClickListener {
sample3()
}
// 取消无 suspend 函数的 Job
button4.setOnClickListener {
sample4()
}
// 超时处理
button5.setOnClickListener {
sample5()
}
// 取消协程
button6.setOnClickListener {
sample6()
}
}
fun sample1() {
CoroutineScope(Dispatchers.Default).launch {
/**
* Job - 通过 launch 返回的对象
* join() - 阻塞,直到 Job 执行完,包括 Job 中的所有子 Job(除了 GlobalScope)
* cancel() - 取消 Job,如果一个 Job 里有子 Job,那么这些子 Job 也都会被取消(除了 GlobalScope)
* cancelAndJoin() - 先 cancel() 然后 join()
* 注:Job 的上述方法必须在协程中或 suspend 函数中调用
*/
val job = launch {
delay(1000)
appendMessage("b")
}
appendMessage("a")
job.join()
appendMessage("c")
}
// a(DefaultDispatcher-worker-2)
// b(DefaultDispatcher-worker-2)
// c(DefaultDispatcher-worker-2)
}
fun sample2() {
CoroutineScope(Dispatchers.Default).launch {
/**
* Job - 通过 launch 返回的对象
* join() - 阻塞,直到 Job 执行完,包括 Job 中的所有子 Job(除了 GlobalScope)
* cancel() - 取消 Job,如果一个 Job 里有子 Job,那么这些子 Job 也都会被取消(除了 GlobalScope)
* cancelAndJoin() - 先 cancel() 然后 join()
* 注:Job 的上述方法必须在协程中或 suspend 函数中调用
*
* 另外:可以通过 joinAll(job1, job2, job3...) 同时 join 多个 Job
*/
val job = launch {
repeat(1000) { i ->
appendMessage("heartbeat $i ...")
delay(500)
}
}
delay(1300)
job.cancel()
job.join()
// job.cancelAndJoin()
appendMessage("done")
}
// heartbeat 0 ...(DefaultDispatcher-worker-1)
// heartbeat 1 ...(DefaultDispatcher-worker-1)
// heartbeat 2 ...(DefaultDispatcher-worker-1)
// done(DefaultDispatcher-worker-1)
}
fun sample3() {
CoroutineScope(Dispatchers.Default).launch {
/**
* 调用 Job 对象的 cancel() 方法,会触发 CancellationException 异常
* 这里有个前提,就是你运行的代码必须是可以检查取消的(声明为 suspend 的函数是可以检查取消的)
* 注:kotlinx.coroutines.* 中定义的方法都是 suspend 的
*/
val job = launch {
repeat(1000) { i ->
try {
appendMessage("heartbeat $i ...")
delay(500)
} catch (e: CancellationException) {
// 运行到 suspend 函数时,如果发现取消了,则会抛出异常,你的 Job 就退出了
// 注意,所有处理程序都会忽略 CancellationException 异常(也就是说抛出此异常并不会导致崩溃),因为它就是用于退出 Job 用的
// 本例仅演示用,实际开发中你可以不必捕获 CancellationException 异常
throw e
}
}
}
delay(1300)
job.cancelAndJoin()
appendMessage("done")
}
// heartbeat 0 ...(DefaultDispatcher-worker-1)
// heartbeat 1 ...(DefaultDispatcher-worker-1)
// heartbeat 2 ...(DefaultDispatcher-worker-1)
// done(DefaultDispatcher-worker-1)
}
fun sample4() {
/**
* 如果你需要取消的 Job 中没有 suspend 该怎么办呢?有两种办法:
* 1、定期调用一个 suspend 函数去检查取消情况
* 2、定期通过 CoroutineScope 的 isActive 检查 Job 是否是活动状态
*/
CoroutineScope(Dispatchers.Default).launch {
val job = launch {
var time = System.currentTimeMillis()
var i = 0
/**
* this 是一个 CoroutineScope 对象
* isCompleted - 是否运行完成
* isCancelled - 是否已取消
* isActive - 是否是活动状态(尚未完成且尚未取消)
*/
while (this.isActive) {
if (System.currentTimeMillis() > time + 500) {
appendMessage("heartbeat ${i++} ...")
time = System.currentTimeMillis()
}
}
}
delay(1300L)
job.cancelAndJoin()
appendMessage("done")
}
// heartbeat 0 ...(DefaultDispatcher-worker-2)
// heartbeat 1 ...(DefaultDispatcher-worker-2)
// done(DefaultDispatcher-worker-2)
}
fun sample5() {
CoroutineScope(Dispatchers.Default).launch {
/**
* withTimeout() - 超时判断,如果超时则触发 TimeoutCancellationException 异常
* withTimeoutOrNull() - 超时判断,如果超时则返回 null(不会触发异常)
* 注:上述方法必须在协程中或 suspend 函数中调用
*/
try {
val result = withTimeout(1300) {
repeat(1000) { i ->
appendMessage("a $i ...")
delay(500)
}
"a done" // 未超时的返回值(超时则触发异常)
}
appendMessage("$result")
} catch (e: TimeoutCancellationException) {
appendMessage(e.toString())
}
// a 0 ...(DefaultDispatcher-worker-1)
// a 1 ...(DefaultDispatcher-worker-1)
// a 2 ...(DefaultDispatcher-worker-1)
// kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms(DefaultDispatcher-worker-1)
try {
val result = withTimeout(5000) {
repeat(2) { i ->
appendMessage("b $i ...")
delay(500)
}
"b done" // 未超时的返回值(超时则触发异常)
}
appendMessage("$result")
} catch (e: TimeoutCancellationException) {
appendMessage(e.toString())
}
// b 0 ...(DefaultDispatcher-worker-1)
// b 1 ...(DefaultDispatcher-worker-1)
// b done(DefaultDispatcher-worker-1)
val result = withTimeoutOrNull(1300) {
repeat(1000) { i ->
appendMessage("c $i ...")
delay(500)
}
"c done" // 未超时的返回值(超时则返回 null)
}
appendMessage("$result")
// c 0 ...(DefaultDispatcher-worker-1)
// c 1 ...(DefaultDispatcher-worker-1)
// c 2 ...(DefaultDispatcher-worker-1)
// null(DefaultDispatcher-worker-1)
val result2 = withTimeoutOrNull(5000) {
repeat(2) { i ->
appendMessage("d $i ...")
delay(500)
}
"d done" // 未超时的返回值(超时则返回 null)
}
appendMessage("$result2")
// d 0 ...(DefaultDispatcher-worker-1)
// d 1 ...(DefaultDispatcher-worker-1)
// d done(DefaultDispatcher-worker-1)
}
}
/**
* CoroutineScope 是可取消的,记得在不需要的时候要取消掉 CoroutineScope
* 取消 CoroutineScope 后,其内所有子也会被取消(但是如果子是 GlobalScope 则不会被取消)
* 取消 CoroutineScope 的操作并不要求非要在协程中或 suspend 函数中调用
* 取消 CoroutineScope 时的检查取消要求和 Job 是类似的,请参见 sample3(), sample4()
*/
private var mainScope = MainScope()
fun sample6() {
mainScope.launch {
repeat(1000) { i ->
appendMessage("heartbeat $i ...")
delay(500)
}
}
}
override fun onDestroy() {
super.onDestroy()
mainScope.cancel()
}
fun appendMessage(message: String) {
val dateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.ENGLISH)
val time = dateFormat.format(Date());
val threadName = Thread.currentThread().name
CoroutineScope(Dispatchers.Main).launch{
val log = "$time: $message($threadName)"
textView1.append(log);
textView1.append("\n");
Log.d("coroutine", log)
}
}
}
/layout/activity_kotlin_coroutine_demo2.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="等待 Job 执行完"/>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="取消 Job"/>
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="取消 Job 需要强调的知识点(声明为 suspend 的函数才可以检查取消)"/>
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="取消无 suspend 函数的 Job"/>
<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="超时处理"/>
<Button
android:id="@+id/button6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="取消协程"/>
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>