Kotlin协程(上)

Kotlin协程(上),第1张

Kotlin协程(上)

参考:
码上开学 | Bilibili
Kotlin: Using Coroutines | Pluralsight
一文快速入门kotlin协程 | 掘金

Kotlin Coroutine

消除并发任务之间的协作难度,打破回调地狱,可以在同一个代码块里实现多线程切换。

launch

launch——创建一个新的协程,并在指定线程上运行它,非阻塞。

launch(Dispatchers.IO){
	val image = getImage(imageId)
}
join

launch返回一个Job对象,通过Job.join()方法可以同步等待协程的完成。

fun main(args: Array) = runBlocking {
    val job = scope.launch { // launch new coroutine and keep a reference to its Job
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    job.join() // wait until child coroutine completes
}
Cancelling coroutine execution

使用 Job.cancel() 方法可以取消一个协程。

val job1 = scope.launch {...} 
val job2 = scope.launch {...} 
//取消一个协程作用域将同时取消此作用域下的所有子协程
scope.cancel()
val job1 = scope.launch { … }
val job2 = scope.launch { … }
// 第一个协程被取消,第二个不受影响
job1.cancel()

job.cancelAndJoin() 替代

job.cancel()
job.join()
Making computation code cancellable

yield()——挂起函数,定期调用来检查取消 *** 作。

fun main(args: Array) = runBlocking {
    val job = scope.launch {
        repeat(1000){
        	print(".")
        	yield()
        	Thread.sleep(1)
        }
    }
    delay(100)
    job.cancelAndJoin()
    println("done")
}

isActive——一个可通过 CoroutineScope 对象在协程内部使用的扩展属性,用于检测协程是否被取消。

fun main(args: Array) = runBlocking {
    val job = scope.launch {
        repeat(1000){
        	if(!isActive) throw CancellationException()//报出异常
        	print(".")
        	Thread.sleep(1)
        }
    }
    delay(100)
    job.cancelAndJoin()
    println("done")
}
fun main(args: Array) = runBlocking {
    val job = scope.launch {
        repeat(1000){
        	if(!isActive) return@launch
        	print(".")
        	Thread.sleep(1)
        }
    }
    delay(100)
    job.cancelAndJoin()
    println("done")
}
CancellationException

可取消的挂起函数在取消时会抛出 CancellationException,可以用常用的方式来处理这种情况。例如,try {...} catch{...} finally {...}。

fun main(args: Array) = runBlocking {
    val job = scope.launch {
    	try {
    	    repeat(1000){
                yield()
                print(".")
                Thread.sleep(1)
            }
    	} catch (ex:CancellationException) {
    		println("cancelled: ${ex.message}")
    	} finally {
    		run(NonCancellable) {//运行不可取消的代码块
    			delay(1000)
                println("In finally")
    		}
            //println("In finally")
    	}
    }
    delay(100)
    //job.cancelAndJoin()
    job.cancel(CancellationException("Reason"))
    job.join()
    println("done")
}

join() 和 cancelAndJoin() 两个函数都会等待所有回收 *** 作完成后再继续执行之后的代码

Timeout

withTimeout()——函数超时返回异常

fun main(args: Array) = runBlocking {
    try{
    	val job = withTimeout(100) {
    	    repeat(1000){
                yield()
                print(".")
                Thread.sleep(1)
            }
    	}
    }catch (ex:TimeoutCancellationException) {
    		println("cancelled: ${ex.message}")
    }
    delay(100)
}

withTimeoutOrNull()——函数在超时时返回Null而不是异常

fun main(args: Array) = runBlocking {
    val job = withTimeoutOrNull(100) {
    	repeat(1000){
            yield()
            print(".")
            Thread.sleep(1)
        }
    }
    
    if(job == null) {
    	println("timeout")
    }
}
runBlocking

runBlocking——创建一个新的协程,但是会阻塞主线程。

runBlocking{
	delay(500)
}
CoroutineContext
@SinceKotlin("1.3")
public interface CoroutineContext

我们可以在每个协程块中访问CoroutineContext,它是Element的索引集,可以使用索引Key来访问上下文中的元素

Job

public interface Job : CoroutineContext.Element

val job = launch {
	println("isActive? ${coroutineContext[Job]!!.isActive})
    //println("isActive? ${coroutineContext[Job.Key]!!.isActive})
}
job.join()

运行结果:isActive? true

CoroutineExceptionHandler

public interface CoroutineExceptionHandler : CoroutineContext.Element

协程的错误处理,可以自定义:

//自定义CoroutineContext
val errorHandle = CoroutineExceptionHandler { context, error ->
    Log.e("Mike","coroutine error $error")
}

//指定自定义CoroutineContext
GlobalScope.launch(context = errorHandle) {
    delay(2000)
    throw Exception("test")
}

打印结果:coroutine error java.lang.Exception: test

Parent-child relationship

如果要让两个协程之间是父子关系,需要flow the coroutineContext from the outer coroutine.

val outer = launch {
	launch(coroutineContext) {//不加coroutineContext就不会形成父子关系,outer协程被取消后不会等待inner协程完成,而是直接取消
		repeat(1000) {
			print(".")
			delay(1)
		}
	}
}
//delay(200)//延迟一段时间让子协程启动
//outer.cancelChildren()//取消子协程
outer.cancelAndJoin()
delay(1000)
println()
println("Outer isCancelled? ${outer.isCancelled}")
  • 父协程如果取消或结束了,那么它下面的所有子协程均被取消或结束。
  • 父协程需等待子协程执行完毕后才会最终进入完成状态,而不管父协程本身的代码块是否已执行完。
  • 子协程被取消,父协程并不会被取消,但是子协程被取消会通知到父协程。
  • 子协程会继承父协程上下文中的元素,如果自身有相同 Key 的成员,则覆盖对应 Key,覆盖效果仅在自身范围内有效。
Combining context elements

给coroutineContext定义多个元素,可以使用+运算符。

fun main() = runBlocking {
    launch(Dispatchers.Default + CoroutineName("test")) {
        println("I'm working in thread ${Thread.currentThread().name}")
    }
}

打印结果:I’m working in thread DefaultDispatcher-worker-1 @test#2

newSingleThreadContext

newSingleThreadContext 用于为协程专门创建一个新的线程来运行。专用线程是非常昂贵的资源。在实际的应用程序中,它必须在不再需要时使用 close 函数释放掉,或者存储在顶级变量中以此实现在整个应用程序中重用。

newSingleThreadContext("STC").use { ctx ->
	val job = launch(ctx) {
		println("STC is working in thread ${Thread.currentThread().name}")
	}
}

这里使用了 kotlin 标准库中的 use 函数用来在不再需要时释放 newSingleThreadContext 所创建的线程。

withContext

withContext——创建新的协程,在指定线程上运行它,执行完后自动切回。

Launch(Dispatchers.Main){
	val image = withContext(Dispatchers.IO){
		getImage(imageId)
	}
	avatarIv.setImageBitmap(image)
}

多个withContext是串行执行,消除并发代码在协作时的嵌套。

Launch(Dispatchers.Main){
	withContext(Dispatchers.IO){
		...
	}
	withContext(Dispatchers.IO){
		...
	}
	...
}

suspend挂起函数,非阻塞式挂起。

当一个协程执行到suspend函数时,会被挂起,从当前线程脱离,执行完suspend函数后再切回来(resume)。

suspend 关键字对协程并没有直接作用,它只是一个提醒,表示该函数是个耗时 *** 作,真正实现挂起需要函数内部直接或间接调用一个协程自带或我们自定义的挂起函数。

耗时函数被自动放在后台执行,让主线程不卡。

suspend fun suspendingGetImage(imageId:String){
	withContext(Dispatchers.IO){//协程自带的挂起函数
		getImage(imageId)
	}
}

launch(Dispatchers.Main){
    ...
    val image = suspendingGetImage(imageId)
    avatarIv.setImageBitmap(image)
}
async

如果同时处理多个耗时任务,且这几个任务都无相互依赖时,可以使用 async … await() 来处理。

btn.setOnClickListener {
    CoroutineScope(Dispatchers.Main).launch {
        val time1 = System.currentTimeMillis()
 
        val task1 = async(Dispatchers.IO) {
            delay(2000)
            Log.e("TAG", "1.执行task1.... [当前线程为:${Thread.currentThread().name}]")
            "one"  //返回结果赋值给task1
        }
 
        val task2 = async(Dispatchers.IO) {
            delay(1000)
            Log.e("TAG", "2.执行task2.... [当前线程为:${Thread.currentThread().name}]")
            "two"  //返回结果赋值给task2
        }
 
        Log.e("TAG", "task1 = ${task1.await()}  , task2 = ${task2.await()} , 耗时 ${System.currentTimeMillis() - time1} ms  [当前线程为:${Thread.currentThread().name}]")
    }
}

运行结果:

2020-03-30 16:00:20.709 : 2.执行task2… [当前线程为:DefaultDispatcher-worker-3]
2020-03-30 16:00:21.709 : 1.执行task1… [当前线程为:DefaultDispatcher-worker-3]
2020-03-30 16:00:21.711 : task1 = one , task2 = two , 耗时 2037 ms [当前线程为:main]

await() 只有在 async 未执行完成返回结果时,才会挂起协程。若 async 已经有结果了,await() 则直接获取其结果并赋值给变量,此时不会挂起协程。

val task1 = async(Dispatchers.IO) {
            delay(2000)
            Log.e("TAG", "1.执行task1.... [当前线程为:${Thread.currentThread().name}]")
            "one"  //返回结果赋值给task1
        }.await()//这样使用的效果与withContext一样
Deferred

async 函数的返回值是一个 Deferred 对象。Deferred 是一个接口类型,继承于 Job 接口,所以 Job 包含的属性和方法 Deferred 都有,其主要就是在 Job 的基础上扩展了 await()方法。

val result: Deferred = doWorkAsync("Hello")
runBlocking {
    println(result.await())
}

fun doWorkAsync(msg: String): Deferred = async {
    log("$msg - Working")
    delay(500)
    log("$msg - Work done")
    return@async 42
}

fun log(msg: String) {
    println("$msg in ${Thread.currentThread().name}")
}

打印结果:

Hello - Working in ForJoinPool.commonPool-worker-1

Hello - Work done in ForJoinPool.commonPool-worker-1

42

Start lazy

CoroutineStart.LAZY不会主动启动协程,而是直到调用async.await()或者async.satrt()后才会启动(即懒加载模式)

fun main() {
    val time = measureTimeMillis {
        runBlocking {
            val asyncA = async(start = CoroutineStart.LAZY) {
                delay(3000)
                1
            }
            val asyncB = async(start = CoroutineStart.LAZY) {
                delay(4000)
                2
            }
            log(asyncA.await() + asyncB.await())
        }
    }
    log(time)
}

[main] 3
[main] 7077

val outer = launch {
	val inner = async(star = CoroutineStart.LAZY) {
		delay(100)
	}
}
outer.join()

使用懒加载后会形成父子协程关系。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5438506.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-11
下一篇 2022-12-11

发表评论

登录后才能评论

评论列表(0条)

保存