Kotlin启动协程的三种方式示例详解

目录
  • 1.launch启动协程
  • 2.runBlocking启动协程
  • 3.async启动协程

1.launch启动协程

fun main() = runBlocking {
    launch {
        delay(1000L)
        println("World!")
    }
    println("Hello")
}
fun main() {
    GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    println("Hello")
    Thread.sleep(2000L)
}
//输出结果
//Hello
//World!

上面是两段代码,这两段代码都是通过launch启动了一个协程并且输出结果也是一样的。

第一段代码中的runBlocking是协程的另一种启动方式,这里先看第二段代码中的launch的启动方式;

  • GlobalScope.launch

GlobalScope.launch是一个扩展函数,接收者是CoroutineScope,意思就是协程作用域,这里的launch等价于CoroutineScope的成员方法,如果要调用launch来启动一个协程就必须先拿到CoroutineScope对象。GlobalScope.launch源码如下

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
}

里面有三个参数:

  • context: 意思是上下文,默认是EmptyCoroutineContext,有默认值就可以不传,但是也可以传递Kotlin提供的Dispatchers来指定协程运行在哪一个线程中;
  • start: CoroutineStart代表了协程的启动模式,不传则默认使用DEFAULT(根据上下文立即调度协程执行),除DEFAULT外还有其他类型:

LAZY:延迟启动协程,只在需要时才启动。

ATOMIC:以一种不可取消的方式,根据其上下文安排执行的协程;

UNDISPATCHED:立即执行协程,直到它在当前线程中的第一个挂起点;

  • block: suspend是挂起的意思,CoroutineScope.()是一个扩展函数,Unit是一个函数类似于Java的void,那么suspend CoroutineScope.() -> Unit就可以这么理解了:首先,它是一个挂起函数,然后它还是CoroutineScope类的成员或者扩展函数,参数为空,返回值类型为Unit
  • delay(): delay()方法从字面理解就是延迟的意思,在上面的代码中延迟了1秒再执行World,从源码可以看出来它跟其他方法不一样,多了一个suspend关键字
//      挂起
//       ↓
public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

suspend的意思就是挂起,被它修饰的函数就是挂起函数, 这也就意味着delay()方法具有挂起和恢复的能力;

  • Thread.sleep(2000L)

这个是休眠2秒,那么这里为什么要有这个呢?要解答这疑问其实不难,将Thread.sleep(2000L)删除后在运行代码可以发现只打印了Hello然后程序就结束了,World!并没有被打印出来。

为什么? 将上面的代码转换成线程实现如下:

fun main() {
    thread(isDaemon = true) {
        Thread.sleep(1000L)
        println("Hello World!")
    }
}

如果不添加isDaemon = true结果输出正常,如果加了那么就没有结果输出。isDaemon的加入后其实是创建了一个【守护线程】,这就意味着主线程结束的时候它会跟着被销毁,所以对于将Thread.sleep删除后导致GlobalScope创建的协程不能正常运行的主要原因就是通过launch创建的协程还没开始执行程序就结束了。那么Thread.sleep(2000L)的作用就是为了不让主线程退出。

另外这里还有一点需要注意:程序的执行过程并不是按照顺序执行的。

fun main() {
    GlobalScope.launch {                // 1
        println("Launch started!")      // 2
        delay(1000L)                    // 3
        println("World!")         	   // 4
    }
    println("Hello")            		// 5
    Thread.sleep(2000L)                 // 6
    println("Process end!")             // 7
}
/*
输出结果:
Hello
Launch started!
World!
Process end!
*/

上面的代码执行顺序是1、5、6、2、3、4、7,这个其实好理解,首先执行1,然后再执行5,执行6的时候等待2秒,在这个等待过程中协程创建完毕了开始执行2、3、4都可以执行了,当2、3、4执行完毕后等待6执行完毕,最后执行7,程序结束。

2.runBlocking启动协程

fun main() {
    runBlocking {                // 1
        println("launch started!")      // 2
        delay(1000L)           // 3
        println("World!")         	    // 4
    }
    println("Hello")            		// 5
    Thread.sleep(2000L)           // 6
    println("Process end!")             // 7
}

上面这段代码只是将GlobalScope.launch改成了runBlocking,但是执行顺序却完全不一样,它的执行顺讯为代码顺序1~7,这是因为runBlocking是带有阻塞属性的,它会阻塞当前线程的执行。这是它跟launch的最大差异。

runBlockinglanuch的另外一个差异是GlobalScope,从代码中可以看出runBlocking并不需要这个,这点可以从源码中分析

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

顶层函数:类似于Java中的静态函数,在Java中常用与工具类,例如StringUtils.lastElement();

runBlocking是一个顶层函数,因此可以直接使用它;在它的第二个参数block中有一个返回值类型:T,它刚好跟runBlocking的返回值类型是一样的,因此可以推测出runBlocking是可以有返回值的

fun main() {
    val result = test(1)
    println("result:$result")
}
fun test(num: Int) = runBlocking {
    return@runBlocking num.toString()
}
//输出结果:
//result:1

但是,Kotlin在文档中注明了这个函数不应该从协程中使用。它的设计目的是将常规的阻塞代码与以挂起风格编写的库连接起来,以便在主函数和测试中使用。 因此在正式环境中这种方式最好不用。

3.async启动协程

在 Kotlin 当中,可以使用 async{} 创建协程,并且还能通过它返回的句柄拿到协程的执行结果。

fun main() = runBlocking {
    val deferred = async {
        1 + 1
    }
    println("result:${deferred.await()}")
}
//输出结果:
//result:2

上面的代码启动了两个协程,启动方式是runBlockingasync,因为async的调用需要一个作用域,而runBlocking恰好满足这个条件,GlobalScope.launch也可以满足这个条件但是GlobalScope也不建议在生产环境中使用,因为GlobalScope 创建的协程没有父协程,GlobalScope 通常也不与任何生命周期组件绑定。除非手动管理,否则很难满足我们实际开发中的需求。

上面的代码多了一个deferred.await()它就是获取最终结果的关键。

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

asynclaunch一样也是一个扩展函数,也有三个参数,和launch的区别在于两点:

  • block的函数类型: launch返回的是Unit类型,async返回的是泛型T
  • 返回值不同: launch返回的是Jobasync返回的是Deffered<T>,而async可以返回执行结果的关键就在这里。

启动协程的三种方式都讲完了,这里存在一个疑问,launchasync都有返回值,为什么async可以获取执行结果,launch却不行?

这主要跟launch的返回值有关,launch的返回值Job代表的是协程的句柄,而句柄并不能返回协程的执行结果。

句柄: 句柄指的是中间媒介,通过这个中间媒介可以控制、操作某样东西。举个例子,door handle 是指门把手,通过门把手可以去控制门,但 door handle 并非 door 本身,只是一个中间媒介。又比如 knife handle 是刀柄,通过刀柄可以使用刀。

协程的三中启动方式区别如下:

  • launch:无法获取执行结果,返回类型Job,不会阻塞;
  • async:可获取执行结果,返回类型Deferred,调用await()会阻塞不调用则不会但也无法获取执行结果;
  • runBlocking:可获取执行结果,阻塞当前线程的执行,多用于Demo、测试,官方推荐只用于连接线程与协程。

以上就是Kotlin启动协程的三种方式示例详解的详细内容,更多关于Kotlin启动协程方式的资料请关注我们其它相关文章!

(0)

相关推荐

  • Room Kotlin API的使用入门教程

    Room 是 SQLite 的封装,它使 Android 对数据库的操作变得非常简单,也是迄今为止我最喜欢的 Jetpack 库.在本文中我会告诉大家如何使用并且测试 Room Kotlin API,同时在介绍过程中,我也会为大家分享其工作原理. 我们将基于 Room with a view codelab 为大家讲解.这里我们会创建一个存储在数据库的词汇表,然后将它们显示到屏幕上,同时用户还可以向列表中添加单词. 定义数据库表 在我们的数据库中仅有一个表,就是保存词汇的表.Word 类代表表中

  • Kotlin协程Context应用使用示例详解

    目录 1.Context的应用 2.万物皆有 Context 1.CoroutineScope 2.Job 3.Dispatcher 4.CoroutineExceptionHandler 1.Context的应用 Context在启动协程模式中就已经遇到过叫CoroutineContext,它的意思就是协程上下文,线程的切换离不开它. 在启动协程模式中也说明过为什么不用传递Context,因为它有一个默认值EmptyCoroutineContext,需要注意的是这个Context是不可以切换线

  • Android使用Kotlin API实践WorkManager

    WorkManager 提供了一系列 API 可以更加便捷地规划异步任务,即使在应用被关闭之后或者设备重启之后,仍然需要保证立即执行的或者推迟执行的任务被正常处理.对于 Kotlin 开发者,WorkManager 为协程提供了最佳的支持.在本文中,我将通过实践 WorkManager codelab 为大家展示 WorkManager 中与协程相关的基本操作.那么让我们开始吧! WorkManager 基础 当您需要某个任务保持运行状态,即使用户切换到别的界面或者用户将应用切换到后台,甚至设备

  • Kotlin协程的线程调度示例详解

    目录 引言 一.协程的分发器作用 1.1 测试代码 1.2 CoroutineScope.launch 1.2.1 newCoroutineContext 1.3 startCoroutineCancellable 1.3.1 intercepted() 1.3.2 CoroutineDispatcher 1.3.3 小结 1.4 DispatchedContinuation 1.5 DefaultScheduler 1.5.1 SchedulerCoroutineDispatcher 1.5.

  • Kotlin协程低级api startCoroutine与ContinuationInterceptor

    目录 聊一聊kotlin协程“低级”api startCoroutine ContinuationInterceptor 实战 kotlin协程api中的 async await 通过startCoroutine与ContinuationInterceptor实现自定义的 async await 本章代码 最后 聊一聊kotlin协程“低级”api Kotlin协程已经出来很久了,相信大家都有不同程度的用上了,由于最近处理的需求有遇到协程相关,因此今天来聊一Kotlin协程的“低级”api,首先

  • node运行js获得输出的三种方式示例详解

    一.通过console.log输出(我最喜欢的) 1.js脚本 1.js var arguments = process.argv.splice(2); //获得入参 var a= arguments[0]; 取第一个 console.log(a) //输出 2.python脚本 test_1.py import os print(os.popen('node 1.js fuck').read()) #打印结果fuck 二.通过文件读写获取 1.js脚本 1.js //npm环境别忘了装了 va

  • Kotlin协程的基础与使用示例详解

    目录 一.协程概述 1.概念 2.特点 3.原理 1)续体传递 2)状态机 二.协程基础 1.协程的上下文 2.协程的作用域 3.协程调度器 4.协程的启动模式 5.协程的生命周期 1)协程状态的转换 2)状态标识的变化 三.协程使用 1.协程的启动 1)runBlocking方法 2)launch方法 3)async方法 4)suspend关键字 5)withContext方法 6)suspend方法 2.协程间通信 1)Channel 2)Channel的容量 3)produce方法与act

  • Android 打包三种方式实例详解

     Android 打包三种方式实例详解 前言: 现在市场上很多app应用存在于各个不同的渠道,大大小小几百个,当我们想要在发布应用之后统计各个渠道的用户下载量,我们就要进行多渠道打包. 01.应用的打包签名什么是打包? 打包就是根据签名和其他标识生成安装包. 签名是什么? 1.在android应用文件(apk)中保存的一个特别字符串 2.用来标识不同的应用开发者:开发者A,开发者B 3.一个应用开发者开发的多款应用使用同一个签名 就好比是一个人写文章,签名就相当于作者的署名. 如果两个应用都是一

  • C#获取本地IP的四种方式示例详解

    1.第一种方式 采用System.Net.Dns的GetHostAddress的方式,具体请看代码: /// <summary> /// 网络不通畅可以获取 /// 不过能获取到具体的IP /// </summary> /// <returns></returns> public static List<IPAddress> GetByGetHostAddresses() { try { IPAddress[] adds = Dns.GetHos

  • Spring依赖注入的三种方式实例详解

    Spring依赖注入(DI)的三种方式,分别为: 1. 接口注入 2. Setter方法注入 3. 构造方法注入 下面介绍一下这三种依赖注入在Spring中是怎么样实现的. 首先我们需要以下几个类: 接口 Logic.java 接口实现类 LogicImpl.java 一个处理类 LoginAction.java 还有一个测试类 TestMain.java Logic.java如下: package com.spring.test.di; public interface Logic { pub

  • Vue路由切换的两种方式示例详解

    目录 Vue路由切换的两种方式 1. 标签切换 1.1 <a>标签切换 1.2 路径切换 1.3 path切换 1.4 name切换(推荐) 2. js切换 Vue路由切换的两种方式 1. 标签切换 1.1 <a>标签切换 语法:<a href=“# + 路由路径”>标签内容</a> 例子: 路由规则为: const router = new VueRouter({ //路由对象数组 routes: [{ path: '/login', component:

  • vue+webpack实现异步加载三种用法示例详解

    1.第一例 const Home = resolve => { import("@/components/home/home.vue").then( module => { resolve(module) } } 注:(上面import的时候可以不写后缀) export default [{ path: '/home', name:'home', component: Home, meta: { requireAuth: true, // 添加该属性可以判断出该页面是否需要

  • Vue自定义组件的四种方式示例详解

    四种组件定义方式都存在以下共性(血泪史) 规则: 1.组件只能有一个根标签 2.记住两个词全局和局部 3.组件名称命名中'-小写字母'相当于大写英文字母(hello-com 相当于 helloCom) 而对于在HTML中自定义组件的时候有4种写法,不过也只是殊途同归,都是用template属性对应的只有一个根标签的HTML代码. 1.全局组件 定义方式示例: Vue.component("hello-component",{ props:["message"], t

  • SpringMVC异步处理的 5 种方式示例详解

    前段时间研究了下 diamond 的原理,其中有个重要的知识点是长连接的实现,用到了 servlet 的异步处理.异步处理最大的好处是可以提高并发量,不阻塞当前线程.其实 Spring MVC 也支持了异步处理,本文记录下相关的技术点. 异步处理 demo 如果要启用异步返回,需要开启 @EnableAsync.如下的代码中,使用 DeferredResult 进行异步处理. 请求进来后,首先创建 DeferredResult 对象,设置超时时间为 60 秒.然后指定DeferredResult

  • c#使用多线程的几种方式示例详解

    (1)不需要传递参数,也不需要返回参数 ThreadStart是一个委托,这个委托的定义为void ThreadStart(),没有参数与返回值. 复制代码 代码如下: class Program { static void Main(string[] args) { for (int i = 0; i < 30; i++) { ThreadStart threadStart = new ThreadStart(Calculate); Thread thread = new Thread(thr

随机推荐