Kotlin协程中launch / join和async / await有什么区别


Answers:


232
  • launch用来开火和忘记协程。这就像启动一个新线程。如果launch终止中的代码异常终止,则将其视为线程中未捕获的异常-通常在后端JVM应用程序中打印到stderr并使Android应用程序崩溃。join用于等待启动的协程的完成,并且不会传播其异常。但是,崩溃的协程也会取消其父级,并带有相应的异常。

  • async用于启动协程以计算结果。结果由的实例表示Deferred,您必须await在实例上使用。async代码中未捕获的异常存储在结果中Deferred,并且不会在其他任何地方传递,除非处理,否则它将被静默删除。您一定不要忘记从async开始的协程


1
异步是Android中网络调用的正确协程生成器吗?
Faraaz

合适的协程制造商取决于您要完成的工作
Roman Elizarov

9
您能否详细说明“您一定不要忘记异步启动的协程”?例如,有没有人会想到的陷阱?
路易斯

2
“异步代码中未捕获的异常存储在生成的Deferred中,不会在其他任何地方传递,除非处理,否则它将被静默丢弃。”
罗曼·伊丽莎拉夫

9
如果您忘记了异步的结果,它将结束并被垃圾回收。但是,如果它由于代码中的某些错误而崩溃,您将永远不会了解。这就是为什么。
Roman Elizarov '18

77

我发现该指南https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md很有用。我将引用关键部分

🦄 协程

本质上,协程是轻量级的线程。

因此,您可以将协程视为一种以非常有效的方式管理线程的事物。

🐤 发射

fun main(args: Array<String>) {
    launch { // launch 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
}

因此,launch启动后台线程,执行某些操作,并立即以返回令牌Job。您可以调用joinJob来阻止直到该launch线程完成

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = launch { // launch new coroutine and keep a reference to its Job
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    job.join() // wait until child coroutine completes
}

sync 异步

从概念上讲,异步就像启动。它启动一个单独的协程,它是一个轻量级线程,可与所有其他协程同时工作。区别在于,启动会返回Job,并且不会携带任何结果值,而异步会返回Deferred,这是一种轻量,无阻塞的未来,表示希望在以后提供结果。

因此,async启动后台线程,执行某些操作,并立即以返回令牌Deferred

fun main(args: Array<String>) = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
}

您可以对延迟值使用.await()以获得其最终结果,但Deferred也是Job,因此可以根据需要取消它。

所以Deferred实际上是一个Job。参见https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html

interface Deferred<out T> : Job (source)

🦋 默认情况下渴望异步

有一个惰性选项,可以使用值为CoroutineStart.LAZY的可选开始参数进行异步。它仅在某些等待结果或调用启动函数时才启动协程。


12

launchasync用于启动新的协程。但是,他们以不同的方式执行它们。

我想展示一个非常基本的例子,它可以帮助您很容易地理解差异

  1. 发射
    class MainActivity : AppCompatActivity() {

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

        btnCount.setOnClickListener {
            pgBar.visibility = View.VISIBLE
            CoroutineScope(Dispatchers.Main).launch {
                val currentMillis = System.currentTimeMillis()
                val retVal1 = downloadTask1()
                val retVal2 = downloadTask2()
                val retVal3 = downloadTask3()
                Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1}, ${retVal2}, ${retVal3} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
                pgBar.visibility = View.GONE
            }
        }

    // Task 1 will take 5 seconds to complete download
    private suspend fun downloadTask1() : String {
        kotlinx.coroutines.delay(5000);
        return "Complete";
    }

    // Task 1 will take 8 seconds to complete download    
    private suspend fun downloadTask2() : Int {
        kotlinx.coroutines.delay(8000);
        return 100;
    }

    // Task 1 will take 5 seconds to complete download
    private suspend fun downloadTask3() : Float {
        kotlinx.coroutines.delay(5000);
        return 4.0f;
    }
}

在此示例中,我的代码是在单击btnCount按钮时下载3个数据,并显示pgBar进度条,直到完成所有下载。有3个suspend功能downloadTask1()downloadTask2()并且downloadTask3()其下载数据。为了模拟它,我delay()在这些函数中使用了。这些功能等待5 seconds8 seconds5 seconds分别。

正如我们用来launch启动这些挂起函数的方法一样,launch将按顺序(一对一)执行它们。这意味着,downloadTask2()将在downloadTask1()完成后开始,并且downloadTask3()仅在downloadTask2()完成后开始。

如输出屏幕截图所示Toast,完成所有3次下载的总执行时间将导致5秒+ 8秒+ 5秒= 18秒,其中launch

启动示例

  1. 异步的

如我们所见,这launch使sequentially所有3个任务都得以执行。完成所有任务的时间为18 seconds

如果这些任务是独立的,并且不需要其他任务的计算结果,则可以使它们运行concurrently。它们将同时启动,并在后台同时运行。可以使用完成此操作async

async返回Deffered<T>type 的实例,其中Tsuspend函数返回的数据类型为。例如,

  • downloadTask1()将返回,Deferred<String>因为String是函数的返回类型
  • downloadTask2()将返回,Deferred<Int>因为Int是函数的返回类型
  • downloadTask3()将返回,Deferred<Float>因为Float是函数的返回类型

我们可以使用asynctype 的返回对象Deferred<T>来获取type 的返回值T。可以通过await()电话完成。例如检查以下代码

        btnCount.setOnClickListener {
        pgBar.visibility = View.VISIBLE

        CoroutineScope(Dispatchers.Main).launch {
            val currentMillis = System.currentTimeMillis()
            val retVal1 = async(Dispatchers.IO) { downloadTask1() }
            val retVal2 = async(Dispatchers.IO) { downloadTask2() }
            val retVal3 = async(Dispatchers.IO) { downloadTask3() }

            Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1.await()}, ${retVal2.await()}, ${retVal3.await()} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
            pgBar.visibility = View.GONE
        }

这样,我们同时启动了所有3个任务。因此,我要完成的总执行时间仅为8 seconds该时间,downloadTask2()因为这是所有3个任务中最大的。您可以在以下屏幕截图中看到这一点Toast message

等待示例


1
谢谢你提的launch连续的玩意儿,而async对于并发
Akbolat SSS

您对所有任务都使用了一次启动,对每个任务都使用了异步。也许是更快,因为每个人都是在另一个协程中发射的,而不是等人吗?这是不正确的比较。通常性能是相同的。一个主要的区别是,启动始终会启动一个新的协程,而不是将所有者拆分为一个的异步。还有一个因素是,如果异步任务之一由于某种原因而失败,则父协程也将失败。这就是为什么异步不如启动流行的原因。
p2lem8dev

1
这个答案是不对的,直接将异步与暂停功能(而不是启动)进行比较。与在示例中直接调用suspend函数不同,如果您调用launch(Dispatchers.IO){downloadTask1()},您会看到两者同时执行,而不是顺序执行,您将无法获取输出,但是会看到不连续。另外,如果您没有将deferred.await()串联起来,并且不分别调用deferred.await(),则会看到异步是顺序的。
Thracian

2
-1这是完全错误的。双方launchasync会启动新的协同程序。您正在将一个没有孩子的协程与一个有3个孩子的协程进行比较。您可以将每个async调用替换为,launch并且绝对不会改变并发性。
Moira

这个答案中的外来噪音增加了协同例程主题之外的复杂性。
trueadjustr

6
  1. 协程生成器(即启动和异步)基本上都是lambda,其接收器的类型为CoroutineScope,这意味着它们的内部块被编译为暂停函数,因此它们都以异步模式运行,并且它们都将顺序执行。

  2. 启动和异步之间的区别在于它们启用两种不同的可能性。启动生成器返回Job,但是async函数将返回Deferred对象。您可以使用launch执行一个不希望有任何返回值的块,即写入数据库或保存文件或处理基本上只是由于其副作用而进行的操作。另一方面,如前所述,返回Deferred的异步从执行其块(包装数据的对象)返回一个有用的值,因此您可以将其主要用于其结果,但也可以用于其副作用。注意:您可以使用await函数去除延迟的值并获取其值,该函数将阻塞语句的执行,直到返回值或引发异常!

  3. 协程生成器(启动和异步)都可以取消。

  4. 还有什么?:是的,如果在其块内引发了异常,则启动启动,协程将自动取消,并传送异常。另一方面,如果异步发生该异常,则该异常不会进一步传播,应该在返回的Deferred对象中捕获/处理。

  5. 有关协程的更多信息https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1


1
感谢您的评论。它收集了线程的所有点。我要补充一点,并非所有发射都被取消,例如,原子弹永远无法被取消。
p2lem8dev

4

发射返回工作

异步返回结果(延迟的作业)

用join启动用于等待作业完成,它只是挂起协程调用join(),同时让当前线程可以自由地做其他工作(例如执行另一个协程)。

异步用于计算一些结果。它创建一个协程并作为Deferred的实现返回其未来结果。取消结果递延操作后,将取消正在运行的协程。

考虑一个返回字符串值的异步方法。如果使用async方法而没有等待,它将返回一个Deferred字符串,但是如果使用了await,您将得到一个字符串作为结果

异步与启动之间的主要区别。协程完成执行后,Deferred返回T类型的特定值,而Job则不然。


0

异步与启动 异步与启动Diff映像

启动/异步没有结果

  • 在不需要结果时使用,
  • 不要在被调用的地方阻塞代码,
  • 并行运行

结果异步

  • 当您需要等待结果并可以并行运行以提高效率时
  • 阻止代码在哪里调用
  • 并行运行
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.