在 kotlinx.coroutines
库中,您可以使用 launch
(与 join
)或 async
(与 await
)启动新的协程。它们之间有什么区别?
launch 用于触发和忘记协程。这就像开始一个新线程。如果启动中的代码因异常而终止,则将其视为线程中未捕获的异常——通常在后端 JVM 应用程序中打印到 stderr 并使 Android 应用程序崩溃。 join 用于等待启动的协程完成,它不会传播其异常。但是,崩溃的子协程也会取消其父协程,并出现相应的异常。
async 用于启动计算某些结果的协程。结果由 Deferred 的实例表示,您必须在其上使用 await。异步代码中未捕获的异常存储在生成的 Deferred 中,并且不会传递到其他任何地方,除非处理,否则它将被静默丢弃。你一定不要忘记你用异步启动的协程。
我发现 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
实际上是一个 Job
。 Read this 了解更多详情。
interface Deferred<out T> : Job (source)
🦋 async 默认是 Eager
有一个惰性选项来异步使用可选的 start 参数,其值为 CoroutineStart.LAZY。它仅在某些 await 需要其结果或调用 start 函数时才启动协程。
join()
是没有意义的。另一件事:launch
示例显然无法编译(launch
需要 CoroutineScope)。
launch
和 async
用于启动新的协程。但是,他们以不同的方式执行它们。
我想展示一个非常基本的例子,它可以帮助你很容易地理解差异
发射
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 seconds
、8 seconds
和 5 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
downloadTask2() 将返回 Deferred
downloadTask3() 将返回 Deferred
我们可以使用 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
launch
和 async
都将启动新的协程。您正在将一个没有孩子的协程与一个有 3 个孩子的协程进行比较。您可以用 launch
替换每个 async
调用,并且在并发方面绝对不会发生任何变化。
两个协程构建器,即 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
Async 和 Launch,两者都用于创建在后台运行的协程。在几乎所有情况下,人们都可以使用它们中的任何一个。
tl;博士版本:
当你不关心任务的返回值,只想运行它时,你可以使用 Launch。如果你需要任务/协程的返回类型,你应该使用异步。
替代方案:但是,我认为上述差异/方法是考虑 Java/每个请求模型一个线程的结果。协程非常便宜,如果你想从某个任务/协程的返回值中做某事(比如说服务调用),你最好从那个创建一个新的协程。如果你想让一个协程等待另一个协程传输一些数据,我建议使用通道而不是 Deferred 对象的返回值。使用通道并根据需要创建尽可能多的协程是 IMO 的更好方法
详细解答:
唯一的区别在于返回类型和它提供的功能。
Launch 返回 Job
,而 Async 返回 Deferred
。有趣的是,Deferred 扩展了 Job。这意味着它必须在 Job 之上提供额外的功能。 Deferred 是类型参数化的,其中 T 是返回类型。因此,延迟对象可以从异步方法执行的代码块中返回一些响应。
ps 我只写了这个答案,因为我在这个问题上看到了一些实际上不正确的答案,并想为大家澄清这个概念。此外,在自己从事宠物项目时,由于以前的 Java 背景,我遇到了类似的问题。
execute in background
,除非您使用 Dispatchers
定义它。你的回答让新手更加困惑
启动返回工作
异步返回结果(延迟作业)
launch
和 join
用于等待作业完成。它只是暂停调用 join()
的协程,同时让当前线程可以自由地做其他工作(如执行另一个协程)。
async
用于计算一些结果。它创建一个协程并将其未来结果作为 Deferred
的实现返回。当产生的 deferred 被取消时,正在运行的协程被取消。
考虑一个返回字符串值的异步方法。如果在没有 await
的情况下使用异步方法,它将返回一个 Deferred
字符串,但如果使用 await
,您将得到一个字符串作为结果
async
和 launch
之间的主要区别:
在 Coroutine 完成执行后,Deferred 会返回 T 类型的特定值,而 Job 不会。
https://i.stack.imgur.com/9NdUa.png
启动/异步没有结果
在不需要结果时使用,
不要阻塞调用的代码,
顺序运行
结果异步
当您需要等待结果并且可以并行运行以提高效率时,
阻塞被调用的代码,
并发运行
除了其他很好的答案之外,对于熟悉 Rx 并进入协程的人来说,async
返回一个类似于 Single
的 Deferred
,而 launch
返回一个更类似于 Completable
的 Job
。您可以.await()
阻止并获取第一个值,.join()
阻止直到 Job
完成。
不定期副业成功案例分享