ChatGPT解决这个技术问题 Extra ChatGPT

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

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


Y
YektaDev

launch 用于触发和忘记协程。这就像开始一个新线程。如果启动中的代码因异常而终止,则将其视为线程中未捕获的异常——通常在后端 JVM 应用程序中打印到 stderr 并使 Android 应用程序崩溃。 join 用于等待启动的协程完成,它不会传播其异常。但是,崩溃的子协程也会取消其父协程,并出现相应的异常。

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


Async 是 Android 中网络调用的正确协程构建器吗?
正确的协程构建器取决于您要完成的工作
你能详细说明“你不能忘记你从异步开始的协程”吗?例如,是否存在人们意想不到的问题?
“异步代码中未捕获的异常存储在生成的 Deferred 中,并且不会传递到其他任何地方,除非处理,否则它将被静默丢弃。”
如果您忘记了异步的结果,它将完成并被垃圾收集。但是,如果它由于您的代码中的某些错误而崩溃,您将永远不会了解这一点。这就是为什么。
Y
YektaDev

我发现 this guide 很有用。我将引用基本部分。

🦄 协程

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

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

🐤推出

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

🦆 异步

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

您可以在延迟值上使用 .await() 来获得其最终结果,但 Deferred 也是一个作业,因此您可以在需要时取消它。

所以 Deferred 实际上是一个 JobRead this 了解更多详情。

interface Deferred<out T> : Job (source)

🦋 async 默认是 Eager

有一个惰性选项来异步使用可选的 start 参数,其值为 CoroutineStart.LAZY。它仅在某些 await 需要其结果或调用 start 函数时才启动协程。


在启动的代码块示例中(在 runBlocking 中使用),我认为您不需要“job.join()”,因为 runBlocking 协程将等待其子进程完成。只有在使用顶级作用域(例如 GlobalScope)创建协程时才需要它。
@Avilio 它没有伤害,虽然可以肯定的是,在这个例子中调用 join() 是没有意义的。另一件事:launch 示例显然无法编译(launch 需要 CoroutineScope)。
K
Kushal

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

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

发射

    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

https://i.stack.imgur.com/tSIfD.png

异步

正如我们所见,launch 为所有 3 个任务执行 sequentially。完成所有任务的时间是 18 seconds

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

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

downloadTask1() 将返回 Deferred 因为 String 是函数的返回类型

downloadTask2() 将返回 Deferred 因为 Int 是函数的返回类型

downloadTask3() 将返回 Deferred 因为 Float 是函数的返回类型

我们可以使用 Deferred<T> 类型的 async 的返回对象来获取 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 中的以下屏幕截图中看到这一点

https://i.stack.imgur.com/SxpMy.png


感谢您提到 launch 用于 sequential 乐趣,而 async 用于 concurrent
您已经为所有任务使用了一次启动,并为每个任务使用了异步。也许它更快,因为每个都在另一个协程中启动并且不等待某人?这是不正确的比较。通常性能是相同的。一个关键的区别是,启动总是启动一个新的协程,而不是拆分所有者的异步。另一个因素是,如果其中一个异步任务由于某种原因失败,则父协程也将失败。这就是为什么 async 不如 launch 受欢迎的原因。
这个答案是不对的,直接比较异步和挂起函数而不是启动。而不是在示例中直接调用挂起函数,如果您调用 launch(Dispatchers.IO) {downloadTask1()} 您将看到两者是同时执行的,而不是顺序执行的,您将无法获得输出,但您会看到它是不是顺序的。此外,如果您不连接 deferred.await() 并单独调用 deferred.await() 您将看到异步是顺序的。
-1 这完全是错误的。 launchasync 都将启动新的协程。您正在将一个没有孩子的协程与一个有 3 个孩子的协程进行比较。您可以用 launch 替换每个 async 调用,并且在并发方面绝对不会发生任何变化。
这个答案中的外来噪音增加了协同程序主题之外的复杂性。
A
AouledIssa

两个协程构建器,即 launch 和 async 基本上都是带有 CoroutineScope 类型接收器的 lambda,这意味着它们的内部块被编译为挂起函数,因此它们都以异步模式运行并且它们都将顺序执行它们的块。启动和异步之间的区别在于它们启用了两种不同的可能性。启动构建器返回一个 Job 但是异步函数将返回一个 Deferred 对象。您可以使用launch 来执行一个您不希望从中返回任何值的块,即写入数据库或保存文件或处理基本上只是为了其副作用而调用的东西。另一方面,异步返回一个 Deferred,正如我之前所说,它从其块的执行中返回一个有用的值,一个包装数据的对象,因此您可以将其主要用于其结果,但也可能用于其副作用。注意:您可以使用函数 await 剥离 deferred 并获取其值,这将阻止语句的执行,直到返回值或引发异常!您可以通过使用函数 join() 来实现与启动相同的事情,协程构建器(启动和异步)都是可取消的。还有更多吗?:是的,如果在其块内引发异常,则启动协程会自动取消并传递异常。另一方面,如果异步发生这种情况,则异常不会进一步传播,应该在返回的 Deferred 对象中捕获/处理。更多关于协程 https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1


感谢您的评论。它收集了线程的所有点。我要补充一点,并不是所有的发射都被取消,例如原子不能被取消。
H
Himanshu Yadav

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

tl;博士版本:

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

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

详细解答:

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

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

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


“Async 和 Launch,两者都用于创建在后台运行的协程”协程不一定意味着 execute in background,除非您使用 Dispatchers 定义它。你的回答让新手更加困惑
D
Dr.jacky

启动返回工作

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

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

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

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

asynclaunch 之间的主要区别:
在 Coroutine 完成执行后,Deferred 会返回 T 类型的特定值,而 Job 不会。


D
Dr.jacky

https://i.stack.imgur.com/9NdUa.png

启动/异步没有结果

在不需要结果时使用,

不要阻塞调用的代码,

顺序运行

结果异步

当您需要等待结果并且可以并行运行以提高效率时,

阻塞被调用的代码,

并发运行


J
JaviCasa

除了其他很好的答案之外,对于熟悉 Rx 并进入协程的人来说,async 返回一个类似于 SingleDeferred,而 launch 返回一个更类似于 CompletableJob。您可以.await() 阻止并获取第一个值,.join() 阻止直到 Job 完成。