Kotlin 协程(1) 基本认识


I. 协程(coroutine) kotlin的线程切换框架

典型的应用:

  • 开启一个协程,指定 CoroutineContex 协程上下文(包含运行的线程及线程池等)
  • 内部包含其它协程(这样的操作较少)、挂起函数(较多)、多线程操作代码(较多)等;
  • 遇到挂起函数,会进入到函数内,切换线程到挂起函数指定的线程,并执行完后,再切回原线程,继续执行。

II. 挂起函数 suspend fun

挂起函数,会挂起协程;挂起,不是阻塞。
最直接的用法就是在主线程中,遇到一个挂起函数,进入到挂起函数内去执行;

如果该挂起函数,又被一个协程指定了线程,那么会切换线程运行;
不管切不切线程,挂起函数执行完后,才继续向下执行。
但是,若不是由一个子协程来指定切线程,那么就不会等到该线程完全执行完毕的。(如使用了ThreadThreadPoolExecutor来启动线程)

decompile 会看到, 挂起函数封装成 Continuation 对象,通过Continuation#resumeWith(result:Result<T>) 获取结果,result.isFailure/isSuccess 挂起函数 失败/成功;外部拿到挂起函数的结果后,再向后执行。

suspend 本身不能让函数被挂起。结合运用在协程内,或在其它挂起函数内来执行挂起操作。
挂起函数,要求被一个协程或另一个挂起函数调用,最终的目的,还是要被协程来运行。

suspend fun m() {
	//withContext 是一个挂起函数
	withContext(Dispatchers.IO) {
		...
	}
}

挂起函数一旦挂起协程(不一定会切换线程),就会暂停协程代码不向下运行,直到挂起函数执行完。

withContext(),kotlin提供的一个挂起函数:

public suspend fun <T> withContext(
		context: kotlin.coroutines.CoroutineContext, 
		block: suspend kotlinx.coroutines.CoroutineScope.() -> T
	) : T {   }

III. 创建协程

a. 协程作用域 CoroutineScope

创建 CoroutineScope:

  • runBlocking() 阻塞式。会等到内部所有子协程与挂起函数执行完成。内部会创建 BlockingCoroutine 阻塞式协程对象
  • MainScope() 标准库中并没有具体的实现(写个控制台demo会报错)。Android中是有的。主线程的协程作用域。
  • GlobalScope 是一个 object class。全局作用域。它不会随父级的非全局作用域的取消而取消。
  • 自定义实现
class MyCoroutineScope(
		override val coroutineContext: CoroutineContext
	) : CoroutineScope

CoroutineScope 的扩展函数:

  • launch() 非阻塞。默认依赖 CoroutineScope 的 CoroutineContext。返回 Job 。内部会创建 StandaloneCoroutine对象(它实现了Job, Continuation, CoroutineScope )。
  • async() 非阻塞。会创建新的 CoroutineContext。内部创建DeferredCoroutine对象(它们Deferred, Job, Continuation, CoroutineScope DefaultDispatcher)

b. launch()

public fun kotlinx.coroutines.CoroutineScope.launch(
		context: kotlin.coroutines.CoroutineContext, 
		start: kotlinx.coroutines.CoroutineStart, 
		block: suspend kotlinx.coroutines.CoroutineScope.() -> kotlin.Unit
	) : kotlinx.coroutines.Job {  }

关于参数:
context : CoroutineContext 协程上下文
start : CoroutineStart 协程启动模式
block : 一个挂起的、CoroutineScope 的、 匿名的扩展函数。内部由函数 coroutineScope(block){} 创建 CoroutineScope 协程作用域 对象。
注: CoroutineScope.() 就像扩展函数一样声明的,只是没有函数名。然而通过测试发现,block的调用,实际上需要传入的是一个CoroutineScope对象,所以在调用block函数的时候会创建CoroutineScope对象。
写了个例子,

扫描二维码关注公众号,回复: 10552538 查看本文章
fun test(v: Int, block: Int.()  -> Double) {
   println(block(v * 2)) //println 2048.0
}
fun calc(a: Int): Double {
   return (a shl 10) * 1.0 //2<<10 = 2048
}
test(1) {
   calc(this)
   //18.5
}

调用test(),传入一个int参数,和一个函数; 即 { calc(this) } 这个整体就是 test中的 block参数;
test() 实现中,打印 block()的返回值; block 需要一个Int形参数,这里对 v 乘以 2;
block <==> { calc(this) } ,这时this = 2,calc函数运行后得到 2048.0;
最后,block 的返回值 2048.0 被 println() 出来
 
如果把注释中的 “18.5” 语句打开,那它就是 block() 的返回值。 kotlin 语法,在这里会把最后一行代码的结果当成返回值。

c. async()

public fun <T> kotlinx.coroutines.CoroutineScope.async(
		context: kotlin.coroutines.CoroutineContext, 
		start: kotlinx.coroutines.CoroutineStart, 
		block: suspend kotlinx.coroutines.CoroutineScope.() -> T
	): kotlinx.coroutines.Deferred<T> { }

d. runBlocking()

public fun <T> runBlocking(
		context: kotlin.coroutines.CoroutineContext,  
		block: suspend kotlinx.coroutines.CoroutineScope.() -> T
	): T {  }

这几个函数在声明时,context 、start 都赋予了默认值。
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,

e. 协程启动模式 CoroutineStart

public enum class CoroutineStart {
    DEFAULT, 						//默认的模式,立即执行协程体
    LAZY,							//只有在需要的情况下运行
    @ExperimentalCoroutinesApi		//实验性 协程 api
    ATOMIC,							//
    @ExperimentalCoroutinesApi
    UNDISPATCHED;					//
}

f. Job

主要成员函数:

  • jon() 等待协程执行完。( 类似 Thread#join() )
  • cancel() 取消协程。协程中的所有挂起函数都是可取消的。它们检查协程的取消,并在取消时抛出CancellationException。
  • cancelAndJoin() 内部就是先cancel()再join()。等待取消操作完成

g. Deferred

它是Job的子接口。 主要有一个 await()
await() 是一个 suspend 函数,它有返回类型<T>。
调用了它,就会挂起协程,执行完后获得一个结果 T 。

h. Dispatchers 调度器

实现自 CoroutineContext 。

public actual object Dispatchers {
	public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
	public actual val Main: MainCoroutineDispatcher get() = 
			MainDispatcherLoader.dispatcher
	public actual val Unconfined: CoroutineDispatcher = 
			kotlinx.coroutines.Unconfined
	public val IO: CoroutineDispatcher = DefaultScheduler.IO
}

Main 安卓中能使用,即 UI 线程;控制台中不能使用,未实现。
IO 内部线程池中的一个dispatcher线程;执行磁盘或网络 I/O 密集型;
Default 内部线程池中的一个dispatcher线程,执行cpu 密集型;
Unconfined 非限制

i. 其它挂起函数

  • delay() 协程挂起多少毫秒。( 类似 Thread.sleep() )
  • yield() 如果可能,将当前协程Dispatchers的线程(或线程池)提供给其他协程序运行。 ( 类似 Thread.yield() ) 。 如下例,会交错输出:
// 同是 Main、Default、Unconfined,才会在 yield 切换运行的协程时,在同一线程中。
// IO 会创建新线程。 Default 内部是一个线程池。
runBlocking {
    launch(Dispatchers.Unconfined, CoroutineStart.LAZY) {
        for (i in 0..3) {
            println("aaaa ${Thread.currentThread().name}")
            yield()
        }
    }
    launch(Dispatchers.Unconfined, CoroutineStart.LAZY) {
        for (i in 0..3) {
            println("bbbb  ${Thread.currentThread().name}")
            yield()
        }
    }
}

IV. 使用流程

  • 先要创建协程作用域(同时,内部就会创建一个协程)
  • 协程作用域内,除普通代码外,可以含有其它协程与挂起函数(当然挂起函数内也可以再有子协程)
  • 可以指定 CoroutineContext、Dispatchers、CoroutineStart
  • 是否需要对协程进行 join、cancel 操作,还是要 await 结果;以此来选择使用 lanuch()还是async();
    Deferred 是 Job的子类,也有 join、cancel 操作,其
  • withContext 是一个挂起函数。 使用它,即不关心 协程的 join、cancel、await 操作

发布了400 篇原创文章 · 获赞 364 · 访问量 162万+

猜你喜欢

转载自blog.csdn.net/jjwwmlp456/article/details/104859936