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

Rom*_*rov 122 asynchronous coroutine kotlin kotlin-coroutines

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

Rom*_*rov 184

  • launch用于解雇和忘记协程.这就像开始一个新线程.如果内部的代码launch以异常终止,那么它在线程中被视为未捕获的异常 - 通常在后端JVM应用程序中打印到stderr并崩溃Android应用程序.join用于等待启动的协同程序的完成,它不会传播其异常.但是,崩溃的协程也会取消其父级,并带有相应的异常.

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

  • 你能详细说明"你绝不能忘记你以异步开始的协程"吗?有没有人会不会期待的例子? (4认同)
  • 如果你忘记了异步的结果,那么它将被垃圾收集.但是,如果由于代码中的某些错误导致崩溃,您将永远不会了解这一点.这就是为什么. (4认同)
  • “异步代码中未捕获的异常存储在生成的 Deferred 中,不会传递到其他任何地方,除非处理,否则它将被静默删除。” (3认同)
  • @RomanElizarov 我认为异常处理的描述现在已经过时了?是否可以更新,因为当您搜索协程异常处理时,这仍然排名很高。或者告诉我我也错了:-)? (3认同)
  • Async 是 Android 中网络调用的正确协程构建器吗? (2认同)
  • 看起来这个关于“async”不报告异常的注释已经过时了。我认为这个回复应该被编辑。 (2认同)

onm*_*133 62

我发现这个指南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
}
Run Code Online (Sandbox Code Playgroud)

所以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
}
Run Code Online (Sandbox Code Playgroud)

异步

从概念上讲,异步就像启动一样.它启动一个单独的协程,这是一个轻量级的线程,与所有其他协同程序同时工作.不同之处在于,启动返回一个Job并且不携带任何结果值,而async返回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")
}
Run Code Online (Sandbox Code Playgroud)

您可以在延迟值上使用.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)
Run Code Online (Sandbox Code Playgroud)

async默认是急切的

使用值为CoroutineStart.LAZY的可选启动参数进行异步时存在惰性选项.只有当某个等待需要其结果或者调用了start函数时,它才会启动协同程序.


Kus*_*hal 16

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,我的代码通过单击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>类型的实例,其中T是我们的挂起函数返回的数据类型。例如,

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

我们可以使用async类型的返回对象Deferred<T>来获取类型中的返回值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
        }
Run Code Online (Sandbox Code Playgroud)

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

等待示例

  • 感谢您提到“launch”用于**顺序**乐趣,而“async”用于**并发** (7认同)
  • -1 这完全是错误的。“launch”和“async”都将启动新的协程。您正在将没有子项的单个协程与有 3 个子项的单个协程进行比较。您可以用“launch”替换每个“async”调用,并且并发性方面绝对不会发生任何变化。 (7认同)
  • 这个答案是不正确的,直接将异步与挂起函数进行比较,而不是启动。在示例中,不要直接调用挂起函数,如果您调用 launch(Dispatchers.IO) {downloadTask1()} 您将看到两者都是同时执行的,而不是**顺序**,您将无法获得输出,但您会发现它不是连续的。另外,如果您不连接 deferred.await() 并单独调用 deferred.await(),您将看到异步是顺序的。 (5认同)
  • 您已对所有任务使用一次启动,并为每个任务使用异步。也许它更快,因为每个协程都是在另一个协程中启动的并且不等待某人?这是一个不正确的比较。通常性能是相同的。一个关键的区别是,launch 总是启动一个新的协程,而不是分割所有者协程的异步协程。另一个因素是,如果其中一个异步任务由于某种原因失败,父协程也会失败。这就是为什么 async 不像 launch 那样流行。 (4认同)
  • 这个答案中的无关噪音增加了协同例程主题之外的复杂性。 (3认同)
  • 这是错误的例子。在启动测试中尝试使用“launch{downloadTask1()}”而不是“downloadTask1()”。 (3认同)
  • 这个答案完全没有抓住重点,而且是完全错误的。应该将其删除。 (3认同)
  • 如果您确实想以这种方式进行比较,您还需要在单独的启动块中“启动”所有 3 个下载任务。否则你就会把事情搞混,让人们感到困惑。如果您添加 `val job1 = launch(Dispatchers.IO) { downloadTask1() }`、`val job2 = ...` 等,那么您将真正按照您建议的 `async` 并行运行它。 (2认同)

moh*_*ssa 13

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

  2. 启动和异步之间的区别在于它们启用了两种不同的可能性。启动构建器返回一个 Job,但是异步函数将返回一个 Deferred 对象。您可以使用 launch 来执行一个您不希望从中获得任何返回值的块,即写入数据库或保存文件或处理基本上只是因其副作用而调用的内容。另一方面, async 返回一个 Deferred ,正如我之前所说的那样,它从执行其块的过程中返回一个有用的值,一个包装数据的对象,因此您可以将它主要用于其结果,但也可能用于其副作用。注意:您可以使用 await 函数剥离 deferred 并获取其值,这将阻止您的语句的执行,直到返回值或抛出异常!

  3. 协程构建器(启动和异步)都是可取消的。

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

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


Mar*_*Gin 8

启动返回作业

\n

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

\n

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

\n

async用于计算一些结果。它创建一个协程并返回其未来结果作为 的实现Deferred。当所产生的延迟被取消时,正在运行的协程也会被取消。

\n

考虑一个返回字符串值的异步方法。如果不使用异步方法,await它将返回一个Deferred字符串,但如果await使用异步方法,您将得到一个字符串作为结果

\n
\n

async和 之间的主要区别launch
\n Deferred 在协程完成执行后返回 T 类型的特定值,而 Job 则不会\xe2\x80\x99t。

\n


小智 5

Async 和 Launch,两者都用于创建在后台运行的协程。在几乎所有情况下,您都可以使用其中的任何一种。

tl;博士版本:

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

替代方案:但是,我觉得上述差异/方法是考虑 Java/每个请求模型一个线程的结果。协程非常便宜,如果你想从某个任务/协程的返回值中做一些事情(比如服务调用),你最好从那个协程创建一个新的协程。如果你想让一个协程等待另一个协程传输一些数据,我建议使用通道而不是来自 Deferred 对象的返回值。使用通道并根据需要创建尽可能多的协程,是 IMO 的更好方法

详细回答

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

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

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