Kotlin协程中的启动/连接和异步/等待之间有什么区别

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

66445 次浏览
  • launch用于启动并忘记协同程序。这就像开始一个新的线程。如果launch中的代码异常终止,那么它将像线程中的未捕获异常一样被处理——通常打印到后端JVM应用程序中的stderr并使Android应用程序崩溃。join用于等待启动的协程的完成,它不传播它的异常。然而,一个崩溃的孩子协程也会取消它的父类和相应的异常。

  • async用于启动一个计算结果的协程。结果由Deferred的实例表示,你必须对它使用awaitasync代码中未捕获的异常存储在结果Deferred中,并且不会传递到其他任何地方,除非经过处理,否则它将被无声地丢弃。Deferred0。

我发现本指南是有用的。我将引用重要的部分。

🦄协同程序

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

所以你可以把协程看作是一种非常有效地管理线程的方法。

🐤发射

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
}

🦆异步

从概念上讲,async就像launch。它启动一个单独的协程,这是一个轻量级线程,与所有其他协程并发工作。区别在于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也是一个Job,所以如果需要,你可以取消它。

所以Deferred实际上是一个Job读到这获取更多详细信息。

interface Deferred<out T> : Job (source)

🦋Async默认是eager

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

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;
}
}

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

因为我们已经使用launch来启动这些挂起函数,所以launch将执行它们顺序(一)。这意味着,downloadTask2()将在downloadTask1()完成后启动,而downloadTask3()将仅在downloadTask2()完成后启动。

正如在输出截图Toast中,完成所有3个下载的总执行时间将导致5秒+ 8秒+ 5秒= 18秒launch

Launch Example

  1. 异步

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

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

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

  • downloadTask1()将返回Deferred<String>作为String函数的返回类型
  • downloadTask2()将返回Deferred<Int>,因为Int是函数的返回类型
  • downloadTask3()将返回Deferred<Float>,因为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截图中看到这一点

await example

< >强启动< / >强返回一个作业

< >强异步< / >强返回一个结果(被延迟的任务)

launchjoin用于等待作业完成。它只是挂起调用join()的协程,同时让当前线程空闲地做其他工作(比如执行另一个协程)。

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

考虑一个返回字符串值的异步方法。如果async方法没有使用await,它将返回一个Deferred字符串,但如果使用await,结果将是一个字符串


asynclaunch的关键区别:
Deferred在协程完成执行后返回类型为T的特定值,而Job则不会。 < / em >

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

  2. launch和async的区别在于它们支持两种不同的可能性。启动构建器返回一个Job,而async函数将返回一个Deferred对象。你可以使用launch来执行一个你不期望从它返回任何值的块,即写入数据库或保存文件或处理一些基本上只是为了它的副作用而调用的东西。另一方面,async返回一个Deferred,就像我之前说的,返回一个有用的值,从它的块的执行,一个对象包装你的数据,所以你可以主要使用它的结果,但也可能是它的副作用。注意:你可以用await函数剥离deferred并获取它的值,这将阻塞语句的执行,直到返回值或抛出异常!你可以通过使用join()函数来实现同样的功能。

  3. 协程构建器(launch和async)都是可取消的。

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

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

enter image description here

启动/异步没有结果

  • 用在不需要结果的时候,
  • 不要阻塞被调用的代码,
  • 按顺序运行

Async for result

  • 当你需要等待结果时,可以并行运行 效率,李< / >
  • 阻塞被调用的代码,
  • 并发运行

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

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

tl;博士版:

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

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

详细的回答:

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

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

附注:我之所以写下这个答案,是因为我在这个问题上看到了一些事实上不正确的答案,我想为大家澄清这个概念。另外,在我自己做一个宠物项目时,由于之前有Java背景,我也遇到过类似的问题。