在 kotlinx.coroutines
库中,您可以使用 launch
(带有join
)或 async
(带有await
)启动新的协程。它们之间有什么区别?
在 kotlinx.coroutines
库中,您可以使用 launch
(带有join
)或 async
(带有await
)启动新的协程。它们之间有什么区别?
我发现这个指南很有用。我将引用其中的关键部分。
协程
本质上,协程是轻量级线程。
因此,您可以将协程视为以非常高效的方式管理线程的东西。
launch
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)
async
从概念上来说就像是launch
。它启动了一个独立的协程,这是一个轻量级的线程,可以与所有其他协程并发地工作。不同之处在于,launch
返回一个Job
但不带任何结果值,而async
则返回一个Deferred
——一个轻量级的非阻塞future对象,表示承诺稍后提供结果。
因此,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 value的最终结果,但是Deferred
也是一个Job
,所以如果需要,你可以取消它。Deferred
实际上是一个Job
。查看此文档以了解更多详情。interface Deferred<out T> : Job (source)
async 默认情况下是急切的
使用可选的 start 参数,并将其值设置为
CoroutineStart.LAZY
,可以提供 async 的惰性选项。仅当某些await
需要该协程的结果或调用启动函数时,才会启动协程。
join()
没有意义。另外一件事:launch
示例根本无法编译(launch
需要CoroutineScope)。 - geigerlaunch和async这两个协程构建器本质上都是带有CoroutineScope接收者的lambda表达式,这意味着它们的内部块被编译为挂起函数,因此它们都以异步模式运行,并且它们都会按顺序执行其块。
launch和async的区别在于它们提供了两种不同的可能性。launch构建器返回一个Job,而async函数将返回一个Deferred对象。你可以使用launch来执行一个你不希望从中返回任何值的块,例如写入数据库、保存文件或处理一些数据,基本上只是为了产生副作用。另一方面,正如我之前所述,返回Deferred的async将从其块的执行中返回一个有用的值,一个包装你的数据的对象,因此你主要可以使用它的结果,但也可能使用它的副作用。注意:你可以使用函数await来剥离Deferred并获取其值,这将阻塞你的语句执行,直到返回一个值或抛出异常!你可以通过使用函数join()来使用launch获得相同的效果。
launch和async这两个协程构建器都是可取消的。
还有什么需要吗?:是的,如果在其块内抛出异常,则协程将自动取消并传递异常。另一方面,如果发生这种情况,则异步异常不会进一步传播,并且应该在返回的Deferred对象中被捕获/处理。
更多关于协程的信息:https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1
launch
和async
用于启动新的协程。但是,它们以不同的方式执行。
我想展示一个非常基本的例子,可以帮助你很容易地理解区别。
- launch
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
进度条。有三个 suspend
函数 downloadTask1()
、downloadTask2()
和 downloadTask3()
,用于下载数据。为了模拟它,我在这些函数中使用了 delay()
。这些函数分别等待 5 秒
、8 秒
和 5 秒
。launch
来启动这些挂起函数,launch
将按顺序(一个接一个)执行它们。这意味着,在 downloadTask1()
完成后,downloadTask2()
将开始,只有在 downloadTask2()
完成后,downloadTask3()
才会开始。Toast
所示,使用 launch
完成所有 3 次下载所需的总执行时间将导致 5 秒 + 8 秒 + 5 秒 = 18 秒。
正如我们所看到的,launch
使得所有三个任务都按顺序执行。完成所有任务的时间为 18 秒
。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
}
这样,我们同时启动了所有3个任务。因此,我的总执行时间将仅为8秒
,这是downloadTask2()
的时间,因为它是3个任务中最大的。您可以在以下截图中的Toast message
中看到这一点。
launch
是用于顺序函数,而async
用于并发函数。 - Akbolat SSSlaunch
进行比较。如果你在示例中直接调用挂起函数,改为使用 launch(Dispatchers.IO) {downloadTask1()}
,你会发现两者是并发执行的,而不是顺序执行,虽然你无法获得输出结果,但你可以看到它们不是按顺序执行的。此外,如果你不连接 deferred.await()
,而是分别调用 deferred.await()
,那么 async
就会变成顺序执行。 - Thracianlaunch
和async
都会启动新的协程。你正在将没有子协程的单个协程与有3个子协程的单个协程进行比较。你可以将每个async
调用替换为launch
,并且并发方面绝对不会有任何变化。 - Salemlaunch{downloadTask1()}
,而不是downloadTask1()
。 - jakchangAsync和Launch都用于创建在后台运行的协程,两种方式在大多数情况下可以互换使用。
简短版本:
如果您不关心任务的返回值,只想执行它,可以使用Launch。 如果您需要任务/协程的返回类型,则应使用Async。
另一种方法:不过,我认为上述差异/方法是考虑Java /每个请求模型线程的后果。 协程非常便宜,如果您想从某些任务/协程(比如服务调用)的返回值中做些什么,最好从那个任务/协程创建一个新的协程。 如果您想让协程等待另一个协程传输数据,我建议使用通道而不是Deferred对象的返回值。 在我的看法中,使用通道和创建所需数量的协程是更好的方式。
详细答案:
唯一的区别在于返回类型和它提供的功能。
Launch返回 Job
,而Async返回 Deferred
。有趣的是,Deferred扩展了Job。这意味着它必须在Job的基础上提供附加功能。Deferred泛型参数化为其中T是返回类型。因此,Deferred对象可以从由async方法执行的代码块中返回一些响应。
附言:我撰写了这篇答案,因为我看到了一些在这个问题上事实不正确的答案,并希望为每个人澄清这个概念。此外,在我自己的宠物项目中遇到类似的问题,因为之前有Java背景。
Dispatchers
定义,否则协程并不一定意味着在后台执行
。您的回答可能会让新手感到困惑。" - Faridlaunch 返回一个工作任务
async 返回一个结果(延迟的任务)
launch
结合 join
,用于等待任务完成。它会暂停协程,并调用 join()
,让当前线程空闲地去做其他事情(例如执行另一个协程)。
async
用于计算一些结果。它创建一个协程,并将其未来的结果以 Deferred
的形式返回。当返回的延迟任务取消时,正在运行的协程也会被取消。
考虑一个返回字符串值的异步方法。如果该异步方法不使用 await
,它将返回一个 Deferred
字符串,但如果使用 await
,则将得到一个字符串作为结果。
async
和 launch
的关键区别:
在您的协程执行完后,Deferred
返回特定类型 T 的一个值,而 Job
不会返回值。
async
返回一个类似于 Single
的 Deferred
,而 launch
返回一个更类似于 Completable
的 Job
。您可以使用 .await()
阻塞并获取第一个值,使用 .join()
阻塞直到完成 Job
。