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

时间:2017-09-14 19:02:59

标签: asynchronous kotlin coroutine kotlin-coroutines

kotlinx.coroutines库中,您可以使用launch(使用join)或async(使用await)启动新的协同程序。他们之间有什么区别?

8 个答案:

答案 0 :(得分:141)

  • launch用于触发并忘记协程。这就像开始一个新线程。如果launch中的代码以异常终止,则它在某个线程中被视为 uncaught 异常 - 通常在后端JVM应用程序中打印到stderr并崩溃Android应用程序。 join用于等待已启动的协同程序的完成,并且它不会传播其异常。但是,崩溃的协程也会取消其父级,并带有相应的异常。

  • async用于启动计算某些结果的协同程序。结果由Deferred的实例表示,您必须使用awaitasync代码中的未捕获异常存储在生成的Deferred内,并且不会在其他任何地方传递,除非经过处理,否则它将被静默删除。 你绝不能忘记你用异步开始的协同程序。

答案 1 :(得分:43)

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

coroutine

  

基本上,协同程序是轻量级线程。

因此,您可以将协程视为以非常有效的方式管理线程的东西。

发布

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。您可以在此join上致电Job以阻止此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
}

async

  

从概念上讲,异步就像启动一样。它启动一个单独的协程,这是一个轻量级的线程,与所有其他协同程序同时工作。不同之处在于,启动返回一个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)

默认情况下async非常渴望

  

使用值为CoroutineStart.LAZY的可选启动参数进行异步时存在懒惰选项。它仅在某些等待需要其结果或调用启动函数时启动协程。

答案 2 :(得分:9)

  1. 这两个协程构建器(即launch和async)基本上都是带有CoroutineScope类型接收器的lambda,这意味着它们的内部块被编译为暂停函数,因此它们都以异步模式运行,并且它们都将顺序执行。

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

  3. 函数在启动时实现相同的目的
  4. 协程构建器(启动和异步)都可以取消。

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

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

答案 3 :(得分:3)

启动 返回工作

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

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

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

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

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

答案 4 :(得分:1)

Async vs Launch Async vs Launch Diff Image

启动/异步无结果

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

异步搜索结果

  • 当您需要等待结果并可以并行运行时 效率
  • 阻止代码在何处
  • 并行运行

答案 5 :(得分:0)

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

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

  
      
  1. 启动
  2.   
    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次下载的总执行时间将导致{strong> 5秒+ 8秒+ 5秒= 18秒 >

Launch Example

  
      
  1. 异步
  2.   

我们看到launch对所有3个任务都执行了launch。完成所有任务的时间为sequentially

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

async返回一个async类型的实例,其中Deffered<T>是我们的暂挂函数返回的数据类型。例如,

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

我们可以使用Deferred<Float>类型的async中的返回对象来获取Deferred<T>类型的返回值。可以通过T调用来完成。例如,查看下面的代码

await()

这样,我们同时启动了所有3个任务。因此,我完成的总执行时间仅为 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 } ,这是8 seconds的时间,因为它是所有3个任务中最大的。您可以在以下downloadTask2()

屏幕截图中看到它

await example

答案 6 :(得分:0)

除了其他很好的答案,对于熟悉Rx并加入协程的人们,async返回与Deferred类似的Single,而launch返回{ {1}}更类似于Job。您可以Completable阻塞并获取第一个值,.await()阻塞直到.join()完成。

答案 7 :(得分:0)

异步和启动,都用于创建在后台运行的协程。在几乎每种情况下,都可以使用它们之一。

tl; dr版本:

当您不关心任务的返回值而只想运行它时,可以使用Launch。如果您需要任务/协程的返回类型,则应使用async。

备用: 但是,我认为上述差异/方法是考虑Java /每个请求模型一个线程的结果。协程是如此便宜,以至于如果您想从某个任务/协程的返回值中做点什么(让我们说一个服务调用),那么最好从该任务/协程中创建一个新的协程。如果您希望协程等待另一个协程传输某些数据,我建议您使用通道而不是Deferred对象的返回值。使用通道并根据需要创建尽可能多的协程,是IMO的更好方法

详细答案

唯一的区别在于返回类型及其提供的功能。

启动返回Job,而异步返回Deferred。有趣的是,Deferred扩展了Job。这意味着它必须在Job之上提供其他功能。延迟是类型化参数,其中T是返回类型。因此,Deferred对象可以从异步方法执行的代码块中返回一些响应。

p.s。我之所以写这个答案,是因为我在这个问题上看到了一些事实不正确的答案,并想为每个人澄清这个概念。另外,由于我以前的Java背景,在自己从事宠物项目时,我也遇到了类似的问题。