开启成长之旅!这是我参与「日新方案 12 月更文应战」的第13天,点击检查活动概况

前面在学习协程发动办法的时分在launch的源码中有一个回来值是Jobasync的回来Deferred也是实现了Job,那么而也便是说launchasync在创立一个协程的时分也会创立一个对应的Job目标。还提到过Job是协程的句柄,那么Job到底是什么?它有什么用?

1.Job的生命周期

先看一下Job的源码,这儿只保留了跟标题相关的内容

public interface Job : CoroutineContext.Element {
    // ------------ 状况查询API ------------
    /**
    * 当该Job处于活动状况时,回来true——它现已开端,没有完结,也没有撤销。
    * 假如没有撤销或失败,等候其子使命完结的Job仍被认为是活动的。
    */
    public val isActive: Boolean
    /**
    * 当Job因任何原因完结时回来true。作业被撤销或失败并已完结其履行也被视为完结。
    * Job只要在所有子使命完结后才算完结。
    */
    public val isCompleted: Boolean
    /**
    *假如该作业因任何原因被撤销,无论是经过显式调用cancel,仍是由于它失败或其子或父作业被撤销,
    * 则回来true。在一般情况下,它并不意味着使命现已完结,由于它或许仍然在完结它正在做的事情,
    * 并等候它的子使命完结。
    */
    public val isCancelled: Boolean
    // ------------ 操控状况API ------------
    /**
    * 假如Job地点的协程还没有被发动那么调用这个办法就会发动协程
    * 假如这个协程被发动了回来true,假如现已发动或许履行结束了回来false
    */
    public fun start(): Boolean
    /**
    * 撤销此Job,可用于指定错误音讯或供给有关撤销原因的其他详细信息
    */
    public fun cancel(cause: CancellationException? = null)
    /**
    * 撤销此Job
    */
    public fun cancel(): Unit = cancel(null)
    public fun cancel(cause: Throwable? = null): Boolean
    // ------------ 等候状况API ------------
    /**
    * 挂起协程,知道使命完结再康复
    */
    public suspend fun join()
    // ------------ 完结状况回调API ------------
    /**
    * 注册Job完结时同步调用的处理程序.
    * 当Job现已完结时,将处理程序将当即调用Job的反常或撤销原因或null
    * 不然,该处理程序将在此Job完结时调用一次。
    */
    public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
    /**
    * 注册在撤销或完结此Job时同步调用的处理程序。
    * 当Job现已被撤销并完结履行时,处理程序将当即调用Job的撤销原因或null,
    * 除非将invokeImmediately设置为false。不然,
    * 当Job撤销或完结时将调用一次handler。
    */
    public fun invokeOnCompletion(
        onCancelling: Boolean = false,
        invokeImmediately: Boolean = true,
        handler: CompletionHandler): DisposableHandle
}

从源码中能够发现这几个函数和变量跟Actviity或许Fragment十分像,所以我们能够总结出两个定论:

  • Job能够监测协程的生命周期
  • Job能够操控协程

在比如中运用这几个函数和变量再来校验一下上面的定论:

fun main() = runBlocking {
    val job = launch {
        delay(1000L)
    }
    job.log()
    job.cancel()
    job.log()
}
fun Job.log() {
    println(
        """
        isActive:$isActive
        isCompleted:$isCompleted
        isCancelled:$isCancelled
        Thread:${Thread.currentThread().name}  
        ================================
    """.trimIndent()
    )
}
//输出成果
//isActive:true
//isCompleted:false
//isCancelled:false
//Thread:main @coroutine#1  
//================================
//isActive:false
//isCompleted:false
//isCancelled:true
//Thread:main @coroutine#1  
//================================

Job.log用了扩展函数,便利调用Job中的状况监测回来值。

上面的代码经过launch创立了一个协程,接收了Job的回来值,这儿用这个job目标做了三件事:

  • 第一个job.log() launch的创立标志着协程现已被发动所以在第一个job.log()的日志中isActive回来值是true;
  • job.cancel() 这儿调用了job的撤销函数将协程使命撤销;
  • 第二个job.log() 上面的代码将协程使命撤销了,然后再次获取协程状况发现isActivte回来false,isCancelled回来true。

上面的代码也印证了前面提出的定论,还有一个函数start没运用,再来调用它之后输出的日志:

fun main() = runBlocking {
    //改变1
    val job = launch(start = CoroutineStart.LAZY) {
        delay(1000L)
    }
    job.log()
    //改变2
    job.start()
    job.log()
    job.cancel()
    job.log()
}
fun Job.log() {
    println(
        """
        isActive:$isActive
        isCompleted:$isCompleted
        isCancelled:$isCancelled
        Thread:${Thread.currentThread().name}  
        ================================
    """.trimIndent()
    )
}
//输出成果:
//isActive:false
//isCompleted:false
//isCancelled:false
//Thread:main @coroutine#1  
//================================
//isActive:true
//isCompleted:false
//isCancelled:false
//Thread:main @coroutine#1  
//================================
//isActive:false
//isCompleted:false
//isCancelled:true
//Thread:main @coroutine#1  
//================================

上面的代码增加了两处修改:

  • 改变1:协程在创立出来的时分就现已被发动,因而为了检查调用Job.start()前的日志需求加上懒发动
  • 改变2:调用start函数发动协程

从输出成果来看没有调用start函数前isActive回来true,调用后就回来了true,当运用懒发动后在调用cancel函数与前面运用cancel函数输出的日志是相同的,能够得知懒发动后对协程的生命周期并没有设么影响(这或许是句废话)。

现在还有最后一个变量没有看isCompleted,在上面的代码中增加一个延时函数,等协程使命结束再打印日志

fun main() = runBlocking {
    val job = launch(start = CoroutineStart.LAZY) {
        delay(1000L)
    }
    job.log()
    job.start()
    job.log()
    job.cancel()
    delay(2000L)		//改变在这儿
    job.log()
}
fun Job.log() {
    println(
        """
        isActive:$isActive
        isCompleted:$isCompleted
        isCancelled:$isCancelled
        Thread:${Thread.currentThread().name}  
        ================================
    """.trimIndent()
    )
}
//输出成果:
//isActive:false
//isCompleted:false
//isCancelled:false
//Thread:main @coroutine#1  
//================================
//isActive:true
//isCompleted:false
//isCancelled:false
//Thread:main @coroutine#1  
//================================
//isActive:false
//isCompleted:true
//isCancelled:true
//Thread:main @coroutine#1  
//================================

从输出成果中看到当调用isCancelisCompleted也回来了true,也便是说使命结束了。

上面的代码为了监测isCompleted的状况加了一个延时函数delay,可是这种办法并不建议运用,由于这个时刻他不是固定的,例如从后台请求数据或许下载文件,这种情况下的时刻是彻底无法预知的。

现在假定现已知道协程履行结束需求delay(1000L)的时刻,假如将协程内的delay时长设置的大于外部的delay时长,会带来什么问题?

fun main() = runBlocking {
    val job = launch(start = CoroutineStart.LAZY) {
        delay(4000L)
    }
    job.log()
    job.start()
    job.log()
    delay(1000L)
    job.log()
    println("Process end!")
}
fun Job.log() {
    println(
        """
        isActive:$isActive
        isCompleted:$isCompleted
        isCancelled:$isCancelled
        Thread:${Thread.currentThread().name}  
        ================================
    """.trimIndent()
    )
}
//输出成果:
//isActive:false
//isCompleted:false
//isCancelled:false
//Thread:main @coroutine#1  
//================================
//isActive:true
//isCompleted:false
//isCancelled:false
//Thread:main @coroutine#1  
//================================
//isActive:true
//isCompleted:false
//isCancelled:false
//Thread:main @coroutine#1  
//================================
//Process end!

由输出成果可知isCompleted状况是false,协程使命是否履行结束不得而知。另外当println("Process end!")履行结束后程序并没有当即输出Process finished with exit code 0,这是由于runBlocking 会一向堵塞,等到 job 使命履行结束今后才真正退出。

那要怎么解决这个问题?

//Job#join 
/**
* 挂起协程,知道使命完结再康复
*/
public suspend fun join()

joinJob中的一个挂起函数,调用后会挂起当前程序的履行流程,等候job当中的协程使命履行结束然后再康复当前程序的履行流程。

join将使命挂起后再康复,那要怎么知道使命是否履行结束了?invokeOnCompletion能够监听使命履行的状况

//Job#invokeOnCompletion
/**
* 注册Job完结时同步调用的处理程序.
* 当Job现已完结时,将处理程序将当即调用Job的反常或撤销原因或null
* 不然,该处理程序将在此Job完结时调用一次。
*/
public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
//Job#invokeOnCompletion
/**
* 注册在撤销或完结此Job时同步调用的处理程序。
* 当Job现已被撤销并完结履行时,处理程序将当即调用Job的撤销原因或null,
* 除非将invokeImmediately设置为false。不然,
* 当Job撤销或完结时将调用一次handler。
*/
public fun invokeOnCompletion(
        onCancelling: Boolean = false,
        invokeImmediately: Boolean = true,
        handler: CompletionHandler): DisposableHandle

joininvokeOnCompletion的运用如下:

fun main() = runBlocking {
    val job = launch(start = CoroutineStart.LAZY) {
        delay(4000L)
    }
    job.log()
    job.start()
    job.log()
    //新增
    job.join()
    //新增
    job.invokeOnCompletion {
        println("==========Task status==========")
        job.log()
    }
    println("Process end!")
}
fun Job.log() {
    println(
        """
        isActive:$isActive
        isCompleted:$isCompleted
        isCancelled:$isCancelled
        Thread:${Thread.currentThread().name}  
        ================================
    """.trimIndent()
    )
}
//输出成果:
//isActive:false
//isCompleted:false
//isCancelled:false
//Thread:main @coroutine#1  
//================================
//isActive:true
//isCompleted:false
//isCancelled:false
//Thread:main @coroutine#1  
//================================
//==========Task status==========
//isActive:false
//isCompleted:true
//isCancelled:false
//Thread:main @coroutine#1  
//================================
//Process end!

能够看到参加joininvokeOnCompletion之后isCompleted的状况就正确了,一起Process end!输出后Process finished with exit code 0也会很快的输出,这说明使命的确履行结束了。

在讲协程的发动办法的时分提出一个观点:launch的回来值Job代表的是协程的句柄。那么Job是协程的句柄该怎么了解?

句柄: 是指一个中心前言,能够操控一个东西。就类似于遥控器操作空调场景中遥控器便是句柄,开关操控灯具场景中开关便是句柄。

所以Job和协程的联系就类似于遥控器和空调,开关和灯具。Job能够监测协程的运转状况也能够操控协程的运转状况。那么Job就和遥控器、开关相同看做是一个句柄。

2.Deffered

launch直接创立了Jobasync经过Deffered间接创立了Job目标,可是它并没有在 Job 的基础上扩展出很多其他功用,而接收一个回来值是依托 await() 办法,那await办法是怎么实现的?

fun main() = runBlocking {
    val deferred = async {
        logX("Coroutine start!")
        delay(1000L)
        logX("Coroutine end!")
        "Coroutine result!"
    }
    val result = deferred.await()
    println("Result = $result")
    logX("Process end!")
}
fun logX(any: Any?) {
    println(
        """
================================
$any 
Thread:${Thread.currentThread().name}
================================
""".trimIndent()
    )
}
//输出成果:
//Coroutine start! 
//Thread:main @coroutine#2
//================================
//================================
//Coroutine end! 
//Thread:main @coroutine#2
//================================
//Result = Coroutine result!
//================================
//Process end! 
//Thread:main @coroutine#1

从输出成果来看,await办法能够获取协程履行成果外,如同还会堵塞协程的履行流程,直到协程使命履行结束。看一下await的源码

//Deferred#await
public interface Deferred<out T> : Job {
    ...
    public suspend fun await(): T
    ...
}

从源码来看await也是一个挂起函数,它跟join是相同的,看似堵塞的过程其实是协程的挂起康复能力。

所以,总的来说,Deferred 只是比 Job 多了一个 await() 挂起函数而已,经过这个挂起函数,就能够等候协程履行结束的一起,还能够直接拿到协程的履行成果。

3.Job与结构化并发

在其他当地看过这么一句话:协程的优势在于结构化并发, 这句话该怎么了解?

这句话能够了解为带有结构和层级的并发,用代码体现就像这样:

fun main() = runBlocking {
    val parentJob: Job
    var childJob1: Job? = null
    var childJob2: Job? = null
    var childJob3: Job? = null
    parentJob = launch {
        childJob1 = launch {
            delay(1000L)
        }
        childJob2 = launch {
            delay(3000L)
        }
        childJob3 = launch {
            delay(5000L)
        }
    }
    delay(500L)
    parentJob.children.forEachIndexed { index, job ->
        when (index) {
            0 -> println("childJob1 === childJob1 is ${childJob1 === job}")
            1 -> println("childJob2 === childJob2 is ${childJob2 === job}")
            2 -> println("childJob3 === childJob3 is ${childJob3 === job}")
        }
    }
    parentJob.join()
    logX("Process end!")
}
//输出成果:
//childJob1 === childJob1 is true
//childJob2 === childJob2 is true
//childJob3 === childJob3 is true
//================================
//Process end! 
//Thread:main @coroutine#1

上面的代码是父子层级,父Job运用launch发动了协程一起它的内部还有三个Job,三个子Job是并发履行的,一起也是用过launch发动的协程,调用了parentJob.join()那么挂起的时刻便是childJob3的时长—5秒,由于它要等候所有使命都履行结束才会康复履行,然后经过children.forEachIndexed进行遍历并别离比照他们与三个子Job的引证是否持平“===”代表了引证持平,即是否是同一个目标)。图示如下

【Kotlin回顾】13.Kotlin协程—Job

前面讲过,Job能够调用cancel办法撤销履行,那么当调用parentJob.cancel会有什么样的情况?

fun main() = runBlocking {
    val parentJob: Job
    var childJob1: Job? = null
    var childJob2: Job? = null
    var childJob3: Job? = null
    parentJob = launch {
        childJob1 = launch {
            println("childJob1 start")
            delay(1000L)
            println("childJob1 end")
        }
        childJob2 = launch {
            println("childJob2 start")
            delay(3000L)
            println("childJob2 start")
        }
        childJob3 = launch {
            println("childJob3 start")
            delay(5000L)
            println("childJob3 start")
        }
    }
    delay(500L)
    parentJob.cancel()
    logX("Process end!")
}
//输出成果:
//childJob1 start
//childJob2 start
//childJob3 start
//================================
//Process end! 
//Thread:main @coroutine#1

parentJob.cancel调用后,每个子Job只是输出了start,这就能够得出一个定论:父Job撤销后子Job也会顺次跟着撤销。假如调用任何一个子Jobcancel则不会对父Job和其他子Job产生影响。

到这儿关于最初的那句协程的优势在于结构化并发就有更更好的了解了,这是Kotlin协程的第二大优势。

4.launch和async的运用场景

  • launch: 首要用来发起一些不需求任何成果的耗时使命,这个使命在履行中能够改变它的履行状况。
  • async: 首要用来发起一些需求成果的耗时使命,以及与挂起函数结合,优化并发。

【Kotlin回顾】13.Kotlin协程—Job