2021-06-07

Kotlin 协程基础Coroutine [kəruː’tiːn]

需要了解的概念,类

1.协程与线程的区别

本质上,协程是轻量级的线程

一个线程中可以有N个协程。协程中也可以有N个子协程。

2.Dispatchers类 -调度器,指定协程运行在哪个线程中

@JvmStatic //与IO共享线程池,区别在于Default限制了最大并发数,最少2个,最大为cpu的核数
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()

@JvmStatic//UI线程
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher

@JvmStatic//未定义的线程,使用这个启动的协程会立即在当前的线程执行
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined

@JvmStatic//一个用于经常IO操作的线程池,告并行量。与Default共享线程池
public val IO: CoroutineDispatcher = DefaultScheduler.IO

3.关于Job类

协程启动后会返回一个job对象,通过此对象,可以手动控制协程的取消等操作。

cancel//取消
join//挂起,等待协程任务执行结束
cancelAndJoin() // 取消该任务并等待它结束

4.挂起函数

suspend 关键字修饰函数,只能运行在协程中。程序运行到该函数时,会被挂起直到该函数执行完成才会继续执行

构造方式

源码:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}
CoroutineStart 的状态

DEFAULT//默认,立即执行,可取消

LAZY //需要手动控制start 时机

ATOMIC //This is similar to [DEFAULT], but the coroutine cannot be cancelled before it starts executing. --协程开始执行前不能取消
 
UNDISPATCHED// 立即在当前线程执行

1.GlobalScope.launch(),启动一个top-level级协程,在Android中,跟随应用的生命周期。如非必要,尽量不要使用。

fun main() {
   GlobalScope.launch(Dispathcer.Main) {
     //.....
   }
    GlobalScope.launch { // 启动一个新的协程并继续
        delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
        println("World!") // 在延迟后打印输出
    }
    println("Hello,") // 协程已在等待时主线程还在继续
   job.join() //等待直到子协程执行结束 
}


2.runBlocking ,在被runBlocking修饰的函数中,只有子协程全部执行结束,函数才会结束。

fun main() = runBlocking<Unit> { // 开始执行主协程
    var job = GlobalScope.launch { // 在后台启动一个新的协程并继续
        delay(1000L)
        println("World!")
    }
    println("Hello,") // 主协程在这里会立即执行
    job.join() // 等待直到子协程执行结束 
}

3.作用域构建器

除了由不同的构建器提供协程作用域之外,还可以使用 coroutineScope 构建器声明自己的作用域。它会创建一个协程作用域并且在所有已启动子协程执行完毕之前不会结束。

fun main() = runBlocking { // this: CoroutineScope
  
    launch { //内嵌launch
        delay(200L)
        println("Task from runBlocking")
    }
    
  
  
    coroutineScope { // 创建一个协程作用域
        launch {
            delay(500L) 
            println("Task from next launch")
        }
        delay(100L)
        println("Task from coroutine scope") // 这一行会在内嵌 launch 之前输出
    }
    
  
    println("Coroutine scope is over") // 这一行在所有协程执行完毕后才输出
}
Task from coroutine scope
Task from runBlocking
Task from next launch
Coroutine scope is over

runBlockingcoroutineScope 主要区别在于,runBlocking 方法会阻塞当前线程来等待, 而 coroutineScope 只是挂起,会释放底层线程用于其他用途。 由于存在这点差异,runBlocking 是常规函数,而 coroutineScope 是挂起函数。

4.CoroutineScope(Dispatchers.Main + viewModelJob).launch

 	class MainViewModel(application: Application) : AndroidViewModel(application) {
   
    /**
     * 这是此 ViewModel 运行的所有协程所用的任务。
     * 终止这个任务将会终止此 ViewModel 开始的所有协程。
     */
    private val viewModelJob = SupervisorJob()

    /**
     * 这是 MainViewModel 启动的所有协程的主作用域。
     * 因为我们传入了 viewModelJob,可以通过调用viewModelJob.cancel() 
     * 来取消所有 uiScope 启动的协程。
     */
    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
   
     fun <T> launchUI() {
        uiScope.launch() {
           //.......省略代码
        }

    }
    
    override fun onCleared() {
        super.onCleared()
        viewModelJob.cancel()
    }
}

AndroidX lifecycle v2.1.0 开始在 ViewModel 类中引入了扩展属性 viewModelScope。可以直接viewModelScope.launch 启动协程,不需要手动cancle。

4.withContext(Dispatcher.xxx),必须在协程体内执行,,带返回结果, 同步关系

private fun launchFromContext() {
        CoroutineScope.launch {
            val time1 = System.currentTimeMillis()
            var task1 = withContext(Dispatchers.IO) {
                delay(200)
                Log.i("kotlin", "io== ${Thread.currentThread().name}")
                1
            }
            var task2 = withContext(Dispatchers.Main) {
                delay(100)
                Log.i("kotlin", "Main== ${Thread.currentThread().name}")
                2
            }
            Log.i("kotlin", "launchFromContext end + $task1 + $task2  time==${System.currentTimeMillis() - time1}"
            )
        }
    }
launchFromContext end
io== DefaultDispatcher-worker-1
Main== main
launchFromContext end + 1 + 2  time==325

task1执行完后,再执行task2,task2执行完之后,才会执行最终的log

5.async 异步关系

  private fun launchFromAysc() {
        CoroutineScope(Dispatchers.Main).launch {
            val time1 = System.currentTimeMillis()
            val task1 = async(Dispatchers.IO) {
                Log.e("kotlin", "1.task1 start")
                delay(5000)
                Log.e("kotlin", "1.执行task1.... [当前线程为:${Thread.currentThread().name}]")
                "one"  //返回结果赋值给task1
            }

            val task2 = async(Dispatchers.IO) {
                Log.e("kotlin", "2.task2 start")
                delay(3000)
                Log.e("kotlin", "2.执行task2.... [当前线程为:${Thread.currentThread().name}]")
                "two"  //返回结果赋值给task2
            }
            Log.e("kotlin", "task1 = ${task1.await()}  , task2 = ${task2.await()} , 耗时 ${System.currentTimeMillis() - time1} ms  [当前线程为:${Thread.currentThread().name}]")
          
           Log.i("kotlin","launchFromAysc end")
        }
    }
1.task1 start]
2.task2 start]
2.执行task2.... [当前线程为:DefaultDispatcher-worker-1]
1.执行task1.... [当前线程为:DefaultDispatcher-worker-1]
task1 = one  , task2 = two , 耗时 5041 ms  [当前线程为:main]

,await是挂起函数,执行到task1.await()时,挂起,接着执行task2.await(),等task1和task2都有数据返回时,才执行最终的打印。

async 就类似于 launch。它启动了一个单独的协程,与其他协程并发工作。不同之处在于 launch 返回一个 Job 并且不附带任何结果值,而 async 返回一个 Deferred —— 一个轻量级的非阻塞 future

Android ViewModel中协程的应用

class MainViewModel(application: Application) : AndroidViewModel(application) {
    private val viewModelJob = SupervisorJob()
    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
    public var data: MutableLiveData<List<Data>> = MutableLiveData()

    fun <T> launchUI(
        block: suspend CoroutineScope.() -> Response<T>,
        one rror: (e: Throwable) -> Unit = {},
        onSuccess: (data: T) -> Unit = {},
        onComplete: () -> Unit = {}
    ) {
        uiScope.launch(CoroutineExceptionHandler { _, throwable -> one rror(throwable) }) {
            var result = block()
            if (result.errorCode == 0) {
                onSuccess(result.data)
            } else {
                one rror(Exception("返回错误码异常" + result.errorCode))
            }
            onComplete()
        }
    }

    fun test() {
        launchUI({
            Log.i("kotlin", "start" + Thread.currentThread().name)
            RetrofitUtils.getService(RequestService::class.java).getDatas().await()
        }, onSuccess = {
            Log.i("kotlin", "onSuccess" + Thread.currentThread().name)
            data.value = it
        }, one rror = {
            Log.i("kotlin", "onError" + it.message)
        }, onComplete = {
            Log.i("kotlin", "onComplete" + Thread.currentThread().name)
        })
    }


    override fun onCleared() {
        super.onCleared()
        viewModelJob.cancel()
    }
}
上一篇:解决FFmmpeg合成的MP4文件播放没声音


下一篇:【初学音视频】解码aac编码格式音频转pcm(使用faad2库)