关于android:Android-Kotlin-协程async

24次阅读

共计 4009 个字符,预计需要花费 11 分钟才能阅读完成。

measureTimeMillis 统计一段代码耗时
内敛函数 measureTimeMillis{} 能够很不便的统计一段代码执行的耗时。
应用:

GlobalScope.launch {
    val time = measureTimeMillis {delay(1000)
        Log.d("zyj-", "日志")
    }
    Log.d("zyj-", "耗时:$time")
}

输入后果:

D/zyj-: 日志
D/zyj-: 耗时:1010

应用默认程序
定义两个耗时函数:

suspend fun doSomethingUsefulOne(): Int {delay(1000L) // 假如咱们在这里做了一些有用的事
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {delay(1000L) // 假如咱们在这里也做了一些有用的事
    return 29
}

应用默认的顺序调用:

val time = measureTimeMillis {val one = doSomethingUsefulOne()
    val two = doSomethingUsefulTwo()
    println("The answer is ${one + two}")
}
println("Completed in $time ms")

它的打印输出如下:

The answer is 42
Completed in 2017 ms

从输入后果上看,两个耗时工作是串行的,总耗时 = 耗时函数 1 + 耗时函数 2

应用 async 并发
如果 doSomethingUsefulOne 与 doSomethingUsefulTwo 之间没有依赖,并且咱们想更快的失去后果,让它们进行 并发 吗?这就是 async 能够帮忙咱们的中央。

在概念上,async 就相似于 launch。它启动了一个独自的协程与其它所有的协程一起并发的工作。不同之处在于 launch 返回一个 Job 并且不附带任何后果值,而 async 返回一个 Deferred—— 一个非阻塞 future,这代表了一个将会在稍后提供后果的 promise。你能够应用 .await()在一个延期的值上失去它的最终后果,然而 Deferred 也是一个 Job,所以如果需要的话,你能够勾销它。

val time = measureTimeMillis {val one = async { doSomethingUsefulOne() }
    val two = async {doSomethingUsefulTwo() }
    println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")

它的打印输出如下:

The answer is 42
Completed in 1017 ms

这里快了两倍,因为两个协程并发执行。请留神,应用协程进行并发总是显式的。

惰性启动的 async
可选的,async 能够通过将 start 参数设置为 CoroutineStart.LAZY 而变为惰性的。在这个模式下,只有后果通过 await 获取的时候协程才会启动,或者在 Job 的 start 函数调用的时候。运行上面的示例:

val time = measureTimeMillis {val one = async(start = CoroutineStart.LAZY) {doSomethingUsefulOne() }
    val two = async(start = CoroutineStart.LAZY) {doSomethingUsefulTwo() }
    // 执行一些计算
    one.start() // 启动第一个
    two.start() // 启动第二个
    println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")

它的打印输出如下:

The answer is 42
Completed in 1017 ms

因而,在先前的例子中这里定义的两个协程没有执行,然而控制权在于程序员精确的在开始执行时调用 start。咱们首先 调用 one,而后调用 two,接下来期待这个协程执行结束。

留神,如果咱们只是在 println 中调用 await,而没有在独自的协程中调用 start,这将会导致程序行为,直到 await 启动该协程 执行并期待至它完结,这并不是惰性的预期用例。在计算一个值波及挂起函数时,这个 async(start = CoroutineStart.LAZY)的用例用于代替规范库中的 lazy 函数。

构建 async 格调的函数
咱们能够定义异步格调的函数来 异步 的调用 doSomethingUsefulOne 和 doSomethingUsefulTwo 并应用 async 协程建造器并带有一个显式的 GlobalScope 援用。咱们给这样的函数的名称中加上“……Async”后缀来突出表明:事实上,它们只做异步计算并且须要应用延期的值来取得后果。

// somethingUsefulOneAsync 函数的返回值类型是 Deferred<Int>
fun somethingUsefulOneAsync() = GlobalScope.async {doSomethingUsefulOne()
}

// somethingUsefulTwoAsync 函数的返回值类型是 Deferred<Int>
fun somethingUsefulTwoAsync() = GlobalScope.async {doSomethingUsefulTwo()
}

应用

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 咱们能够在协程里面启动异步执行
        val one = somethingUsefulOneAsync()
        val two = somethingUsefulTwoAsync()

        // 然而期待后果必须调用其它的挂起或者阻塞
        // 当咱们期待后果的时候,这里咱们应用 `GlobalScope.launch{ }` 来阻塞主线程
        GlobalScope.launch {println("The- answer is ${one.await()}  ${two.await()}")
        }
    }
}

这种带有异步函数的编程格调仅供参考,因为这在其它编程语言中是一种受欢迎的格调。在 Kotlin 的协程中应用这种格调是强烈不举荐的,起因如下所述。

考虑一下如果 val one = somethingUsefulOneAsync() 这一行和 one.await() 表达式这里在代码中有逻辑谬误,并且程序抛出了异样以及程序在操作的过程中停止,将会产生什么。通常状况下,一个全局的异样解决者会捕捉这个异样,将异样打印成日记并报告给开发者,然而反之该程序将会继续执行其它操作。然而这里咱们的 somethingUsefulOneAsync 依然在后盾执行,尽管如此,启动它的那次操作也会被终止。这个程序将不会进行结构化并发,如下一大节所示。

应用 async 的结构化并发
让咱们应用应用 async 的并发这一大节的例子并且提取出一个函数并发的调用 doSomethingUsefulOne 与 doSomethingUsefulTwo 并且返回它们两个的后果之和。因为 async 被定义为了 CoroutineScope 上的扩大,咱们须要将它写在作用域内,并且这是 coroutineScope 函数所提供的:

suspend fun concurrentSum(): Int = coroutineScope {val one = async { doSomethingUsefulOne() }
    val two = async {doSomethingUsefulTwo() }
    one.await() + two.await()
}

这种状况下,如果在 concurrentSum 函数外部产生了谬误,并且它抛出了一个异样,所有在作用域中启动的协程都会被勾销。

val time = measureTimeMillis {println("The answer is ${concurrentSum()}")
}
println("Completed in $time ms")

从下面的 main 函数的输入能够看出,咱们依然能够同时执行这两个操作:

The answer is 42
Completed in 1017 ms

勾销始终通过协程的层次结构来进行传递:

import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    try {failedConcurrentSum()
    } catch(e: ArithmeticException) {println("Computation failed with ArithmeticException")
    }
}

suspend fun failedConcurrentSum(): Int = coroutineScope {
    val one = async<Int> { 
        try {delay(Long.MAX_VALUE) // 模仿一个长时间的运算
            42
        } finally {println("First child was cancelled")
        }
    }
    val two = async<Int> {println("Second child throws an exception")
        throw ArithmeticException()}
    one.await() + two.await()
}

请留神,如果其中一个子协程(即 two)失败,第一个 async 以及期待中的父协程都会被勾销:

Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException

正文完
 0