В чем разница между запуском / объединением и асинхронным / ожиданием в сопрограммах Kotlin

В kotlinx.coroutines Библиотека, вы можете начать новую сопрограмму, используя либо launchjoin) или же asyncawait). В чем разница между ними?

2 ответа

Решение
  • launchиспользуется, чтобы запустить и забыть сопрограмму. Это как начать новую тему. Если код внутри launch завершается с исключением, затем обрабатывается как необработанное исключение в потоке - обычно выводится на stderr в приложениях JVM бэкэнда и приводит к сбою приложений Android. join используется для ожидания завершения запущенной сопрограммы и не распространяет ее исключение. Однако, аварийный дочерний сопрограмма отменяет своего родителя также с соответствующим исключением.

  • async используется для запуска сопрограммы, которая вычисляет некоторый результат. Результат представлен экземпляром Deferred и вы должны использовать await в теме. Неисследованное исключение внутри async код хранится внутри результирующего Deferred и не доставлен куда-либо еще, он будет автоматически отброшен, если не будет обработан. Вы НЕ ДОЛЖНЫ забывать о сопрограмме, которую вы начали с async.

Я считаю это руководство https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md полезным. Я процитирую основные части

сопрограммная

По сути, сопрограммы являются легкими нитями.

Таким образом, вы можете думать о сопрограмме как о чем-то, что очень эффективно управляет потоком.

запуск

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 - это как запуск. Он запускает отдельную сопрограмму, которая представляет собой легкую нить, которая работает одновременно со всеми другими сопрограммами. Разница в том, что запуск возвращает задание и не несет никакого результирующего значения, в то время как async возвращает отложенное - легкое неблокирующее будущее, которое представляет обещание предоставить результат позже.

Так 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, См. https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html

interface Deferred<out T> : Job (source)

Async стремится по умолчанию

Существует опция лени для асинхронизации с использованием необязательного параметра запуска со значением CoroutineStart.LAZY. Он запускает сопрограмму только тогда, когда его результат необходим некоторому ожиданию или если вызывается функция запуска.

launch а также asyncиспользуются для запуска новых сопрограмм. Но исполняют их по-разному.

Я хотел бы показать очень простой пример, который поможет вам очень легко понять разницу.

  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индикатор выполнения, пока не будет завершена вся загрузка. Есть 3suspend функции downloadTask1(), downloadTask2() а также downloadTask3()который скачивает данные. Чтобы смоделировать это, я использовалdelay()в этих функциях. Эти функции ждут5 seconds, 8 seconds а также 5 seconds соответственно.

Как мы использовали launch для запуска этих приостановленных функций, launchвыполнит их последовательно (один за другим). Это значит, что,downloadTask2() начнется после downloadTask1() завершается и downloadTask3() начнется только после downloadTask2() завершается.

Как на выходном скриншоте Toast, общее время выполнения для завершения всех трех загрузок составит 5 секунд + 8 секунд + 5 секунд = 18 секунд сlaunch

  1. асинхронный

Как мы видели, launch совершает казнь sequentiallyдля всех 3-х задач. Время выполнить все задания было18 seconds.

Если эти задачи независимы и им не нужен результат вычислений другой задачи, мы можем заставить их запускаться concurrently. Они будут запускаться одновременно и одновременно работать в фоновом режиме. Это можно сделать с помощью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 seconds что время для downloadTask2()поскольку это самая большая из всех трех задач. Вы можете увидеть это на следующем снимке экрана вToast message

  1. оба конструктора сопрограмм, а именно запуск и асинхронность, в основном являются лямбда-выражениями с приемником типа CoroutineScope, что означает, что их внутренний блок компилируется как функция приостановки, поэтому они оба работают в асинхронном режиме, И они оба будут выполнять свой блок последовательно.

  2. Разница между запуском и асинхронным запуском заключается в том, что они предоставляют две разные возможности. Конструктор запуска возвращает задание, однако асинхронная функция возвращает отложенный объект. Вы можете использовать запуск для выполнения блока, который вы не ожидаете от него какого-либо возвращаемого значения, т.е. записи в базу данных или сохранения файла, или обработки чего-то, что в основном вызвано его побочным эффектом. С другой стороны, async, который возвращает Deferred, как я уже говорил ранее, возвращает полезное значение из выполнения своего блока, объекта, который обертывает ваши данные, поэтому вы можете использовать его в основном для его результата, но, возможно, и для его побочного эффекта. NB: вы можете удалить отложенное значение и получить его значение с помощью функции await, которая заблокирует выполнение ваших операторов до тех пор, пока не будет возвращено значение или не возникнет исключение!Вы можете добиться того же самого с запуском, используя функцию join()

  3. оба конструктора сопрограмм (запуск и асинхронность) могут быть отменены.

  4. что-нибудь еще?: да, с запуском, если в его блоке возникает исключение, сопрограмма автоматически отменяется и исключения доставляются. С другой стороны, если это происходит с async, исключение не распространяется дальше и должно быть перехвачено / обработано в возвращенном отложенном объекте.

  5. подробнее о сопрограммах https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.htmlhttps://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1

запуск возвращает работу

async возвращает результат (отложенное задание)

Запуск с соединением используется для ожидания завершения задания. он просто приостанавливает сопрограмму, вызывающую join(), оставляя текущий поток свободным для выполнения другой работы (например, выполнения другой сопрограммы).

async используется для вычисления некоторых результатов. Он создает сопрограмму и возвращает ее будущий результат как реализацию Deferred. Выполняющаяся сопрограмма отменяется при отмене результирующего отложенного выполнения.

Рассмотрим асинхронный метод, который возвращает строковое значение. Если метод async используется без ожидания, он вернет отложенную строку, но если используется ожидание, вы получите строку в качестве результата

Ключевое различие между async и launch. Deferred возвращает определенное значение типа T после того, как ваша сопрограмма завершает выполнение, а Job - нет.

Async и Launch, оба используются для создания сопрограмм, которые работают в фоновом режиме. Практически в любой ситуации можно использовать любой из них.

tl;dr версия:

Если вас не волнует возвращаемое значение задачи и вы просто хотите ее запустить, вы можете использовать Launch. Если вам нужен тип возврата из задачи / сопрограммы, вы должны использовать async.

Альтернатива: однако я считаю, что указанная выше разница / подход является следствием мышления в терминах Java/ один поток на модель запроса. Сопрограммы настолько недороги, что если вы хотите что-то сделать из возвращаемого значения некоторой задачи / сопрограммы (скажем, вызова службы), вам лучше создать новую сопрограмму на основе этого. Если вы хотите, чтобы сопрограмма ожидала, пока другая сопрограмма передаст некоторые данные, я бы рекомендовал использовать каналы, а не возвращаемое значение из отложенного объекта. Использование каналов и создание необходимого количества сопрограмм - лучший способ IMO

Подробный ответ:

Единственная разница заключается в типе возвращаемого значения и функциональных возможностях, которые он предоставляет.

Запуск возвращается Job пока Async возвращает Deferred. Интересно, что Deferred расширяет Job. Это означает, что он должен предоставлять дополнительные функции поверх Job. Отложенный тип параметризован, где T - тип возвращаемого значения. Таким образом, объект Deferred может возвращать некоторый ответ от блока кода, выполняемого методом async.

ps Я написал этот ответ только потому, что увидел несколько фактически неверных ответов на этот вопрос и хотел прояснить концепцию для всех. Кроме того, когда я сам работал над домашним проектом, я столкнулся с аналогичной проблемой из-за предыдущего фона Java.

Асинхронный против запуска Асинхронный против запуска дифференциального изображения

запуск / асинхронный без результата

  • Используйте, когда результат не нужен,
  • Не блокируйте код, в котором вызывается,
  • Работать параллельно

асинхронный для результата

  • Когда нужно дождаться результата и можно запустить параллельно для эффективности
  • Заблокируйте код, в котором вызывается
  • работать параллельно

Наряду с другими отличными ответами для людей, знакомых с Rx и разбирающихся в сопрограммах, async возвращает Deferred что сродни Single пока launch возвращает Job это больше похоже на Completable. Вы можете.await() чтобы заблокировать и получить значение первого, и .join() заблокировать, пока Job завершено.

Другие вопросы по тегам