kotlin 协程可以开始入门了

协程

学习过程中,老容易忘,还是写点东西,觉得更实在一点。我觉得看API的描述还是很有必要的,所以把一些API翻译了一遍.
(翻译分析自:协程基础
有官翻中文,参见:协程基础(官翻中文)

1.第一个协程程序

kotlin是轻量级的线程,用以稍微替换开销较大的线程的执行。
首先看并发的任务的执行。


fun main() {
    GlobalScope.launch {
        // 在启动一个新的协程并继续
        for (i in 1..1000)
            println("launch $i   ${currentThread().name}") //打印i
    }
    for (i in 1..1000) {
        println("main $i ${currentThread().name}")
    }
    Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}

main 1 main

main 2 main

main 3 main

...........

launch 1   DefaultDispatcher-worker-1

launch 2   DefaultDispatcher-worker-1

launch 3   DefaultDispatcher-worker-1

..........

main 847 main

main 848 main

main 849 main

.............

我们在这里打印循环,并打印线程的名字。
launch 的线程名字是DefaultDispatcher-worker-1,内部开启的一个线程
main打印的线程名字是 main,是主线程
输出结果这样的:我们明确地看出来是并发执行的。

GlobalScope.launch方法

我们看GlobalScope.launch的方法声明。

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {....}

接收三个参数,

  • context: CoroutineContext :这是一个协程上下文对象,一般作用是保存协程开启地方的上下文,类似Android中的上下文。
  • start: CoroutineStart :这是一个开始选项,具体稍后描述。
  • block: suspend CoroutineScope.() -> Unit:这里是接收一个suspend修饰的函数(函数作为方法参数,是kotlin区别于java的一大特点)
    下面是又臭又长的api 介绍:

       public fun CoroutineScope.launch(

            context: CoroutineContext = ...,

            start: CoroutineStart = ...,

            block: suspend CoroutineScope.() → Unit

        ): Job
        Launches new coroutine without blocking current thread and returns a 
reference to the coroutine as a Job. The coroutine is cancelled when the
resulting job is cancelled.

(以不阻塞当前线程的方式,开启一个协程,产生并且返回一个指向当前协程的引用:job,当产生的job被取消时候,这个协程则被取消。)

        Coroutine context is inherited from a CoroutineScope, additional context elements can be specified with context argument. If the context does not have any dispatcher nor any other ContinuationInterceptor, then Dispatchers.Default is used. The parent job is inherited from a CoroutineScope as well, but it can also be overridden with corresponding coroutineContext element.

(协程的上下文继承自CoroutineScope,另外上下文元素可以用context 这个形参进行明确指定。如果上下文中没有一个dispatcher或者是其他的ContinuationInterceptor,那么就会使用Dispatchers.Default。亲代的job也是继承自CoroutineScope,这也可以通过对应的coroutineContext的元素进行重载。)
        By default, the coroutine is immediately scheduled for execution. Other start options can be specified via start parameter. See CoroutineStart for details. An optional start parameter can be set to CoroutineStart.LAZY to start coroutine lazily. In this case, the coroutine Job is created in new state. It can be explicitly started with start function and will be started implicitly on the first invocation of join.
(默认的,这个协程会立刻被安排执行。其他的开始选项可以使用start这个参数进行指定。查看CoroutineStart这个类了解更多的细节。start这个可选择的参数可以设置为CoroutineStart.LAZY,可以实现协程的懒加载。这样协程Job创建出来就是一个全新的状态,可以使用start函数进行显示开启,也可以使用join方法进行隐式开启。)
        Uncaught exceptions in this coroutine cancel parent job in the context by default (unless CoroutineExceptionHandler is explicitly specified), which means that when launch is used with the context of another coroutine, then any uncaught exception leads to the cancellation of parent coroutine.
(默认情况下,在这个协程中未被捕获的异常,会取消掉亲代的Job,除非CoroutineExceptionHandler被明确地指定。这意味着,当launch 在其他协程中调用时候,任何未被捕获的异常都会导致父协程被取消。)

        See newCoroutineContext for a description of debugging facilities that are available for newly created coroutine.
(对于新创建的协程,查看newCoroutineContext 这个类,获取调试技巧的描述。)
        Params:

        context - additional to CoroutineScope.coroutineContext context of the coroutine.
        start - coroutine start option. The default value is CoroutineStart.DEFAULT.
        block - the coroutine code which will be invoked in the context of the provided scope.

  协程是轻量级的线程,我们在这里通过GlobalScope(全局范围)创建了一个协程,这这意味着这个新创建的协程运行时间受到整个程序运行时间的限制。
看下面一个例子:


import kotlinx.coroutines.*

​

fun main() {

    GlobalScope.launch { // 在后台启动一个新的协程并继续

        delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)

        println("World!") // 在延迟后打印输出

    }

    println("Hello,") // 协程已在等待时主线程还在继续

    Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活

}

打印结果如下:

Hello,

World!

  我们在程序中调用了delay函数。这是一个被suspend修饰的函数,在kotlin中,被suspend修饰的函数叫做挂起函数。挂起函数仅仅可以在协程中或者在其他挂起函数中被调用。挂起函数不会阻塞线程,但是会将协程挂起。(挂起是什么概念?可以认为是在协程中暂时停止运行,等待结果的返回,然后再继续运行的一种机制)

 delay()函数

delay()函数的 api


kotlinx.coroutines DelayKt.class public suspend fun delay(

    timeMillis: Long  //延迟的毫秒数

): Unit
Delays coroutine for a given time without blocking a thread and resumes it after a specified time. This suspending function is cancellable. If the Job of the current coroutine is cancelled or completed while this suspending function is waiting, this function immediately resumes with CancellationException.

(根据给定的时间延迟协程,并且在指定的时间中之后,恢复协程.这个挂起函数是可以取消的,如果在这个挂起函数在等待的时间里,这个协程的Job已经取消掉了,或者已经完成,那么在这个挂起函数恢复时候,会抛出异常CancellationException.)
Note that delay can be used in select invocation with onTimeout clause.

(注意,delay也可以搭配onTimeout语句在select操作符中进行使用.)

Implementation note: how exactly time is tracked is an implementation detail of CoroutineDispatcher in the context.
(注意,时间跟踪的准确度是context 里面CoroutineDispatcher 的实现细节)

Params:
timeMillis - time in milliseconds.

在上面的例子中,我们使用了阻塞的Thread.sleep()方法和协程挂起非阻塞的delay()函数,现在我们把Thread.sleep()去掉.


fun main(args:Array<String>) {

   val job= GlobalScope.launch { // launch a new coroutine in background and continue

        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)

        println("World!") // print after delay
    }

    println("Hello,") // main thread continues while coroutine is delayed

    //Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}

运行发现如下结果:

 

Hello,

  没有world,是因为我们先前提起过,"我们在这里通过GlobalScope(全局范围)创建了一个协程,这这意味着这个新创建的协程运行时间受到整个程序运行时间的限制",这个例子中,协程还在等待中,但是整个程序的运行事件结束了,所以协程也提前草草结束了.而当前的协程又是非阻塞的,所以整个程序一下子就运行完事了,我们需要不用Thread.sleep就能把线程阻塞住的方法.
如下面的例子:


import kotlinx.coroutines.*

​

fun main() { 

    GlobalScope.launch { // launch a new coroutine in background and continue

        delay(1000L)
        println("World!")
    }

    println("Hello,") // main thread continues here immediately
    runBlocking {     // but this expression blocks the main thread
        delay(2000L)  // ... while we delay for 2 seconds to keep JVM alive

    } 

}

输出结果:

 

Hello,
World!

我们用runBlocking操作符替换了Thread.sleep,调用了这个操作符的主线程会一直阻塞, 直到runBlocking里面的协程程序执行完毕.

runBlocking函数

看看runBlocking的api介绍.


public fun <T> runBlocking(

    context: CoroutineContext, //协程上下文

    block: suspend CoroutineScope.() → T// 等待运行的挂起函数

): T

Runs a new coroutine and blocks the current thread interruptibly until its completion. This function should not be used from a coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.

(运行一个新的协程,并且阻塞当前的线程,直到本身的结束.这个方法不应该在协程中使用,它设计来就是为了把常见的阻塞代码连接到程序中,一般在main函数或者测试用例中使用.)

The default CoroutineDispatcher for this builder is an internal implementation of event loop that processes continuations in this blocked thread until the completion of this coroutine. See CoroutineDispatcher for the other implementations that are provided by kotlinx.coroutines.

(这个协程构建里面默认的CoroutineDispatcher是一个关于事件循环处理器的内部实现,事件循环处理器管理这个阻塞线程里面的需要继续执行的任务,直到这个协程的结束.查看这个类CoroutineDispatcher,了解kotlinx.coroutines提供的更多实现.)

When CoroutineDispatcher is explicitly specified in the context, then the new coroutine runs in the context of the specified dispatcher while the current thread is blocked. If the specified dispatcher is an event loop of another runBlocking, then this invocation uses the outer event loop.
(在当前线程被阻塞时候,如果CoroutineDispatcher在当前上下文中被明确指定的话,那么新的协程运行在那个指定dispatcher的上下文中.如果这个指定的dispatcher 是另外一个runBlocking操作符的事件循环处理器的话,那么这个操作符就是使用的外层事件循环处理器.)

If this blocked thread is interrupted (see Thread.interrupt), then the coroutine job is cancelled and this runBlocking invocation throws InterruptedException.

(如果这个阻塞的线程被中断了,参考 Thread.interrupt,那么协程的Job会被取消,并且runBlocking操作符抛出InterruptedException的异常.)
See newCoroutineContext for a description of debugging facilities that are available for a newly created coroutine.

(对于新创建的协程,查看newCoroutineContext 这个类,获取调试技巧的描述。)

Params:

context - the context of the coroutine. The default value is an event loop on the current thread.

block - the coroutine code.

上文中的例子也可以用一种更加惯用的方式,使用runBlocking包裹整个main函数。


import kotlinx.coroutines.*

​

fun main() = runBlocking { // start main coroutine

    GlobalScope.launch { // launch a new coroutine in background and continue

        delay(1000L)

        println("World!")

    }

    println("Hello,") // main coroutine continues here immediately
    delay(2000L)      // delaying for 2 seconds to keep JVM alive
}

在别的协程执行时候等待一段时间可不是一个好的方式,现在我们显式地进行等待(以非阻塞的方式),直到后台的任务执行完毕.


import kotlinx.coroutines.*

​

fun main() = runBlocking {

    val job = GlobalScope.launch { // launch a new coroutine and keep a reference to its Job

        delay(1000L)

        println("World!")
    }
    println("Hello,")
    job.join() // wait until child coroutine completes    

}

join函数:

Job.join()


public abstract suspend fun join(): Unit

Suspends coroutine until this job is complete. This invocation resumes normally (without exception) when the job is complete for any reason and the Job of the invoking coroutine is still active. This function also starts the corresponding coroutine if the Job was still in new state.

    (挂起协程,直到job结束。在job以任何原因结束时候,并且调用这个操作符的协程job仍然是活跃状态时候,会正常再恢复协程。当一个job处于全新状态时候,这个方法也会使得这个协程处开始。)

Note, that the job becomes complete only when all its children are complete.

This suspending function is cancellable and always checks for the cancellation of invoking coroutine's Job. If the Job of the invoking coroutine is cancelled or completed when this suspending function is invoked or while it is suspended, this function throws CancellationException.

    (注意,存在且仅存在当所有子协程都结束的时候,这个job才会结束.这个挂起函数是可以被取消的,并且这个函数总是会检查调用者job的取消状态。当这个挂起函数被调用时候,或者被挂起时候,如果调用这个方法的协程的job是被取消的,或者已经完成了,则抛出异常CancellationException。)

In particular, it means that a parent coroutine invoking join on a child coroutine that was started using launch(coroutineContext) { ... } builder throws CancellationException if the child had crashed, unless a non-standard CoroutineExceptionHandler if installed in the context.

    (特别地,一个亲代协程调用了子协程的join方法,子协程是使用launch(coroutineContext) { ... } 来进行构建的,并且子协程已经崩溃掉,那么亲代协程就会抛出CoroutineExceptionHandler异常,除非context上下文中使用了一个非标准的CoroutineExceptionHandler。)

This function can be used in select invocation with onJoin clause. Use isCompleted to check for completion of this job without waiting.

There is cancelAndJoin function that combines an invocation of cancel and join.
    (这个方法可以在select操作符中通过onJoin使用,使用非等待的isCompleted来检查这个job的完成状态。还有一个cancelAndJoin方法,是cancel 操作符和join的集合。)

  

协程程序还有一些需要改进的地方。尽管协程是轻量级的,但是当其运行时候仍然会占用一些内存。比如说,协程程序里面的代码挂起了怎么办(比如说我们delay了太长时间)?如果我们开启了太多的协程内存耗尽怎么办?保持协程的引用手动进行管理吗?那挺容易出错的。
  一个更好的方法,使用结构化并发,在指定的作用域内创建协程,而不是使用GlobalScope这样一个全局作用域(就像线程,线程也是全局作用域)。

   在我们的例子中,我们使用main函数,并且使用runBlocking这个协程构建器连接一下,其实每个协程构建器(包括这个runBlocking)都会给下面的代码块添加一个CoroutineScope协程作用域的实例。我们可以在这个作用域内开启协程,而不需要显式调用join。因为外层的亲代协程(例子中是runBlocking)在内部的子协程完成关闭前,是不会关闭的。因此,我们的代码可以更加简单一点。


import kotlinx.coroutines.*

​
fun main() = runBlocking { // this: CoroutineScope
    launch { // launch a new coroutine in the scope of runBlocking
        delay(1000L)

        println("World!")
    }
    println("Hello,")

}

Scope 作用域构建器

   除了不同的协程构建器可以提供协程作用域之外,我们也可以使用coroutineScope 这个构建器,自己创建协程作用域。由此创建了一个协程作用域,除非内部的子协程全部完成,否则这个协程作用域 不会关闭的。runBlocking[coroutineScope]的不同一点就是,后者在等待所有子协程完成的时候,并不会阻塞当前的线程。
看下面的例子:


import kotlinx.coroutines.*

​

fun main() = runBlocking { // this: CoroutineScope

    launch { 

        delay(200L)
        println("Task from runBlocking")

    }
​

    coroutineScope { // Creates a coroutine scope

        launch {
            delay(500L) 
            println("Task from nested launch")

        }

​
        delay(100L)
        println("Task from coroutine scope") // This line will be printed before the nested launch
    }
​
    println("Coroutine scope is over") // This line is not printed until the nested launch completes
}

我们还可以把阻塞耗时代码抽取出来,单独封装一个挂起函数,就像下面这个样子。


import kotlinx.coroutines.*

​

fun main() = runBlocking {

    launch { doWorld() }

    println("Hello,")

}

​

// this is your first suspending function
suspend fun doWorld() {
    delay(1000L)//模拟耗时操作,比如网络请求或者是文件读取
    println("World!")
}

最后我们看两个比较相似的例子:
例子1:


fun main() = runBlocking {

    GlobalScope.launch {
        repeat(4) { i ->
            println("I'm sleeping $i ...")
            println("inner ${this.coroutineContext} $i ...")

            delay(500L)

        }

    }

    println("outer ${this.coroutineContext} ")

    delay(1300L) // just quit after delay

}

输出结果:


I'm sleeping 0 ...

outer [BlockingCoroutine{Active}@5a2e4553, BlockingEventLoop@28c97a5] 

inner [StandaloneCoroutine{Active}@2510e65f, DefaultDispatcher] 0 ...

I'm sleeping 1 ...

inner [StandaloneCoroutine{Active}@2510e65f, DefaultDispatcher] 1 ...

I'm sleeping 2 ...
inner [StandaloneCoroutine{Active}@2510e65f, DefaultDispatcher] 2 ...

例子2:


fun main() = runBlocking {

    launch {

        repeat(4) { i ->

            println("I'm sleeping $i ...")

            println("inner ${this.coroutineContext} $i ...")

            delay(500L)

        }

    }

    println("outer ${this.coroutineContext} ")
    delay(1300L) // just quit after delay
}

输出结果:


outer [BlockingCoroutine{Active}@6659c656, BlockingEventLoop@6d5380c2] 

I'm sleeping 0 ...

inner [StandaloneCoroutine{Active}@2957fcb0, BlockingEventLoop@6d5380c2] 0 ...

I'm sleeping 1 ...

inner [StandaloneCoroutine{Active}@2957fcb0, BlockingEventLoop@6d5380c2] 1 ...
I'm sleeping 2 ...

inner [StandaloneCoroutine{Active}@2957fcb0, BlockingEventLoop@6d5380c2] 2 ...

I'm sleeping 3 ...

inner [StandaloneCoroutine{Active}@2957fcb0, BlockingEventLoop@6d5380c2] 3 ...

 

两个例子,只有一个单词的差别,第一个例子打印了3次,第二个例子打印了四次。
如果你认真读文章了就应该知道,因为第一个例子里面协程上下文,作用域是全局范围GlobalScope,生命周期收到程序生命周期的限制,并不是亲代协程的子协程,第二个作用域是继承自父协程runBlocking,是属于亲代协程的协程,亲代协程就等待子协程运行完毕后再关闭自身。

第一篇,完结。

(CSDN好麻烦)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值