coroutine 陷阱

https://www.youtube.com/watch?v=CuWJBcOuNHI

学习视频

1. 在service创建自己的servicescope的记得加上supervisorJob,防止coroutine中一个任务失败了,导致其他的同一个scope的任务也一起cancel了,所以加上supervisorJob

supervisorJob()

private val serviceScope = CoroutineScope(Dispatchers.Default+ SupervisorJob())

supervisorJob 的子任务如果失败了,各自不受到影响

但是如果supervisorjob有父任务,父任务取消或者失败,那么supervisorJob和所有的子任务也会取消,如果子任务重出现了异常,除了cancellation异常之外,那么父job也会被取消

如果没有supervisorJob(),会导致serviceScope重的任务,如果有一个取消或者失败了,其他的任务也失败了,所以加上supervisorJob之后,可以保持任务之间的相对独立。

2.另外一个,看下下面 的代码:

suspend fun createFile(context:Context){
    val file = File(context.filesDir,"my_text.txt")
    try {
        writeLines(file)
    }finally {
        deletefile(file)
    }
}

private suspend fun writeLines(file: File){
    withContext(Dispatchers.IO){
        repeat(100_000){
            file.appendText("this is a line")
            ensureActive()//如果取消了,则抛出异常
        }
    }
}
private suspend fun deletefile(file: File){
    withContext(Dispatchers.IO){
        file.delete()
    }
}

我们在activity中启动createFile,

     lifecycleScope.launch {
          val testJob =    launch {  createFile(applicationContext) }
            delay(50L)
            testJob.cancel()
        }

我们启动 createFile 任务,任务向文件写入10万行的数据,需要耗费比较多的时间,然后50毫秒后,我们取消了测试任务,因为有writeline中有 ensureActive,即任务被取消的时候,会抛出cancellation异常,那么writeline因异常终止,然后到了finally,正常来说,finally中的 deletefile应该执行,但是deletefile在同一个launch scope,所以delete中的任务,也没有执行。

writeline异常后,Finnish中的所有suspend的方法都不再执行

为了让delete确保执行,可以加上withContext(NonCancellable)

所以在Finnaly中可以谨慎的使用

try{

}finnaly{

        withContext(NonCancellable){ //确保deleteFile不会被取消

                deleteFile(file)

        }

}

2.异常被吃掉了


suspend fun fetchNetwork(client: HttpClient) {
    while (true) {
        try {
            println("fetch.....start")
            client.get(urlString = "https://www.baidu.com/")
            println("fetch.....end")
            delay(30000)
        } catch (e: Exception) {
            e.printStackTrace()
            println("exception $e")
        }

    }
}

我们在viewmodel中启动循环获取

class Secrets3Viewmodel : ViewModel() {

    init {
        viewModelScope.launch {
            fetchNetwork(HttpClient.client)
        }
    }
}

当界面离开,无论代码运行到delay,还是client。get,他们都有active检查,这时候因为界面退出,viewmodelscope取消,fetchNetwork抛出了cancellation excetion,,但是cancellation exception被catch被吃掉了,这个时候,coroutine没有收到取消异常,继续执行,while循环只是跳过了suspend方法,其他的打印仍在继续

导致coroutine没有收到,导致while true中的方法一直在运行

修改:

suspend fun fetchNetwork(client: HttpClient) {
    while (true) {
        try {
            println("fetch.....start")
            client.get(urlString = "https://www.baidu.com/")
            println("fetch.....end")
            delay(30000)
        } catch (e: Exception) {
            if(e is CancellationException) throw  e
            e.printStackTrace()
            println("exception $e")
        }

    }
}

或者

suspend fun fetchNetwork(client: HttpClient) {
    while (true) {
        try {
            println("fetch.....start")
            client.get(urlString = "https://www.baidu.com/")
            println("fetch.....end")
            delay(30000)
        } catch (e: Exception) {
//            if(e is CancellationException) throw  
            coroutineContext.ensureActive() //继续抛出CancellationException
            e.printStackTrace()
            println("exception $e")
        }

    }
}

都可以继续抛出CancellationException异常,这样while循环才会退出来

4. 框架类的,定义dispatcher provider,提供一个dispatcher 的provider,便于在测试中制定 dispatcher

package com.plcoding.ktoruploadfilewithprogressbar

import android.content.Context
import android.net.Uri
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext


class ImageReader(private val context: Context,val dispatcherProvider: DispatcherProvider) {
    suspend fun readImages(uri: Uri): ByteArray {
        return withContext(dispatcherProvider.io) {
            context.contentResolver.openInputStream(uri)?.use {
                it.readBytes() //文件比较小的,全部读取
            } ?: byteArrayOf()
        }

    }
}


interface DispatcherProvider {
    val main: CoroutineDispatcher
    val io: CoroutineDispatcher
    val default: CoroutineDispatcher
}

class DefalutDispatcherProvider : DispatcherProvider {
    override val main: CoroutineDispatcher
        get() = Dispatchers.Main
    override val io: CoroutineDispatcher
        get() = Dispatchers.IO
    override val default: CoroutineDispatcher
        get() = Dispatchers.Default

}

class TestDispatcherProvider(private val testDispatcher: CoroutineDispatcher) : DispatcherProvider {
    override val main: CoroutineDispatcher
        get() = testDispatcher
    override val io: CoroutineDispatcher
        get() = testDispatcher
    override val default: CoroutineDispatcher
        get() = testDispatcher

}

5. 在读取大文件或者长时间的操作中,记得要 ensureactive确保任务没有被取消,否则ensureactive可以保证抛出cancellation exception,防止一直运行无用的任务,也可以使用yield,切换

suspend fun readImages(uri: Uri): Serializable {
        return withContext(dispatcherProvider.io) {
            context.contentResolver.openInputStream(uri)?.use { inputStream ->
//                it.readBytes() //文件比较小的,全部读取
                ByteArrayOutputStream().use { outputStream ->
                    var bytes = inputStream.read()
                    while (bytes != -1) {
                        outputStream.write(bytes)
                        bytes = inputStream.read()

                        //yield() 两个选一个就可以了,当任务取消了,代码到这里,就会抛出异常
                       // ensureActive()
                    }
                    outputStream.toByteArray()
                }
            } ?: byteArrayOf()
        }

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值