在kotlinx。协程库,您可以使用启动(使用join)或异步(使用await)启动新的协程。它们之间的区别是什么?


launch is used to fire and forget coroutine. It is like starting a new thread. If the code inside the launch terminates with exception, then it is treated like uncaught exception in a thread -- usually printed to stderr in backend JVM applications and crashes Android applications. join is used to wait for completion of the launched coroutine and it does not propagate its exception. However, a crashed child coroutine cancels its parent with the corresponding exception, too. async is used to start a coroutine that computes some result. The result is represented by an instance of Deferred and you must use await on it. An uncaught exception inside the async code is stored inside the resulting Deferred and is not delivered anywhere else, it will get silently dropped unless processed. You MUST NOT forget about the coroutine you’ve started with async.


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

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

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

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

异步

正如我们所看到的,启动会依次执行所有3个任务。完成所有任务的时间是18秒。

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

async返回一个deferred <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秒,这是downloadTask2()的时间,因为它是所有3个任务中最大的。你可以在下面的吐司消息截图中看到这一点


启动返回一个作业

Async返回一个结果(延迟作业)

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

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

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


async和launch之间的关键区别: Deferred在协程完成执行后返回类型为T的特定值,而Job则不会。


both coroutine builders namely launch and async are basically lambdas with receiver of type CoroutineScope which means their inner block is compiled as a suspend function, hence they both run in an asynchronous mode AND they both will execute their block sequentially. The difference between launch and async is that they enable two different possibilities. The launch builder returns a Job however the async function will return a Deferred object. You can use launch to execute a block that you don't expect any returned value from it i.e writing to a database or saving a file or processing something basically just called for its side effect. On the other hand async which return a Deferred as I stated previously returns a useful value from the execution of its block, an object that wraps your data, so you can use it for mainly its result but possibly for its side effect as well. N.B: you can strip the deferred and get its value using the function await, which will block the execution of your statements until a value is returned or an exceptions is thrown! You could achieve the same thing with launch by using the function join() both coroutine builder (launch and async) are cancelable. anything more?: yep with launch if an exception is thrown within its block, the coroutine is automatically canceled and the exceptions is delivered. On the other hand, if that happens with async the exception is not propagated further and should be caught/handled within the returned Deferred object. more on coroutines https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1


启动/异步没有结果

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

Async for result

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


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


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

tl;博士版:

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

Alternate: However, I feel the above difference/approach is a consequence of thinking in terms of Java/one thread per request model. Coroutines are so inexpensive, that if you want to do something from the return value of some task/coroutine(lets say a service call) you are better off creating a new coroutine from that one. If you want a coroutine to wait for another coroutine to transfer some data, I would recommend using channels and not the return value from Deferred object. Using channels and creating as much number of coroutines as required, is the better way IMO

详细的回答:

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

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

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