在kotlinx.coroutines
库中,您可以使用launch
(使用join
)或async
(使用await
)启动新的协同程序。他们之间有什么区别?
答案 0 :(得分:141)
答案 1 :(得分:43)
我发现本指南https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md很有用。我将引用基本部分
coroutine
基本上,协同程序是轻量级线程。
因此,您可以将协程视为以非常有效的方式管理线程的东西。
发布
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
。您可以在此join
上致电Job
以阻止此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
从概念上讲,异步就像启动一样。它启动一个单独的协程,这是一个轻量级的线程,与所有其他协同程序同时工作。不同之处在于,启动返回一个Job并且不携带任何结果值,而异步则返回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
。见https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html
interface Deferred<out T> : Job (source)
默认情况下async非常渴望
使用值为CoroutineStart.LAZY的可选启动参数进行异步时存在懒惰选项。它仅在某些等待需要其结果或调用启动函数时启动协程。
答案 2 :(得分:9)
这两个协程构建器(即launch和async)基本上都是带有CoroutineScope类型接收器的lambda,这意味着它们的内部块被编译为暂停函数,因此它们都以异步模式运行,并且它们都将顺序执行。
启动和异步之间的区别在于它们启用两种不同的可能性。启动生成器返回Job,但是async函数将返回Deferred对象。您可以使用launch执行一个不希望有任何返回值的块,即写入数据库或保存文件或处理基本上只是出于副作用的内容。另一方面,异步(如前所述)返回Deferred会从执行其块(包装数据的对象)中返回一个有用的值,因此您可以将其主要用于其结果,但也可以用于其副作用。 N.B:您可以使用await函数删除延迟的并获取其值,该函数将阻塞语句的执行,直到返回值或引发异常!您可以使用join()
协程构建器(启动和异步)都可以取消。
还有什么?:是的,如果在其块内引发了异常,则启动,协程将自动取消,并交付异常。另一方面,如果异步发生该异常,则该异常不会进一步传播,应该在返回的Deferred对象中捕获/处理。
更多有关协程https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1
答案 3 :(得分:3)
启动 返回工作
异步 返回结果(延迟的作业)
使用join启动用于等待作业完成,它只是挂起了协程调用join()的操作,同时让当前线程可以自由地做其他工作(例如执行另一个协程)。
async 用于计算一些结果。它创建一个协程并作为Deferred的实现返回其未来结果。取消结果递延操作后,将取消正在运行的协程。
考虑一个返回字符串值的异步方法。如果使用了async方法而没有等待,它将返回一个Deferred字符串,但是如果使用了await,您将得到一个字符串作为结果
异步与启动之间的主要区别。在协程完成执行后, Deferred返回特定的T类型值,而Job则没有。
答案 4 :(得分:1)
Async vs Launch Async vs Launch Diff Image
启动/异步无结果
异步搜索结果
答案 5 :(得分:0)
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次下载的总执行时间将导致{strong> 5秒+ 8秒+ 5秒= 18秒 >
- 异步
我们看到launch
对所有3个任务都执行了launch
。完成所有任务的时间为sequentially
。
如果这些任务是独立的,并且它们不需要其他任务的计算结果,则可以使它们运行18 seconds
。它们将同时启动,并在后台同时运行。可以通过concurrently
完成。
async
返回一个async
类型的实例,其中Deffered<T>
是我们的暂挂函数返回的数据类型。例如,
T
将返回downloadTask1()
,因为String是函数的返回类型Deferred<String>
将返回downloadTask2()
,因为Int是函数的返回类型Deferred<Int>
将返回downloadTask3()
,因为Float是函数的返回类型我们可以使用Deferred<Float>
类型的async
中的返回对象来获取Deferred<T>
类型的返回值。可以通过T
调用来完成。例如,查看下面的代码
await()
这样,我们同时启动了所有3个任务。因此,我完成的总执行时间仅为 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
}
,这是8 seconds
的时间,因为它是所有3个任务中最大的。您可以在以下downloadTask2()
答案 6 :(得分:0)
除了其他很好的答案,对于熟悉Rx并加入协程的人们,async
返回与Deferred
类似的Single
,而launch
返回{ {1}}更类似于Job
。您可以Completable
阻塞并获取第一个值,.await()
阻塞直到.join()
完成。
答案 7 :(得分:0)
异步和启动,都用于创建在后台运行的协程。在几乎每种情况下,都可以使用它们之一。
tl; dr版本:
当您不关心任务的返回值而只想运行它时,可以使用Launch。如果您需要任务/协程的返回类型,则应使用async。
备用: 但是,我认为上述差异/方法是考虑Java /每个请求模型一个线程的结果。协程是如此便宜,以至于如果您想从某个任务/协程的返回值中做点什么(让我们说一个服务调用),那么最好从该任务/协程中创建一个新的协程。如果您希望协程等待另一个协程传输某些数据,我建议您使用通道而不是Deferred对象的返回值。使用通道并根据需要创建尽可能多的协程,是IMO的更好方法
详细答案:
唯一的区别在于返回类型及其提供的功能。
启动返回Job
,而异步返回Deferred
。有趣的是,Deferred扩展了Job。这意味着它必须在Job之上提供其他功能。延迟是类型化参数,其中T是返回类型。因此,Deferred对象可以从异步方法执行的代码块中返回一些响应。
p.s。我之所以写这个答案,是因为我在这个问题上看到了一些事实不正确的答案,并想为每个人澄清这个概念。另外,由于我以前的Java背景,在自己从事宠物项目时,我也遇到了类似的问题。