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

156

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

Роман Елизаров
источник

Ответы:

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

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

Роман Елизаров
источник
1
Async - правильный компаньон для сетевых вызовов в Android?
Фарааз
Правильный строитель сопрограмм зависит от того, чего вы пытаетесь достичь
Роман Елизаров
9
Можете ли вы уточнить «Вы НЕ ДОЛЖНЫ забывать о сопрограмме, которую вы начали с async»? Есть ли ошибки, которые нельзя ожидать, например?
Луис
2
«Непонятное исключение в асинхронном коде хранится в получающемся отсроченном и не доставляется куда-либо еще, оно молча удаляется, если не будет обработано»
Роман Елизаров
9
Если вы забудете результат async, то он закончится и будет собирать мусор. Однако, если он выйдет из строя из-за какой-то ошибки в вашем коде, вы никогда не узнаете об этом. Поэтому.
Роман Елизаров
77

Я считаю это руководство 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
}

Yn асинхронный

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

Yn асинхронный по умолчанию

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

onmyway133
источник
11

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индикатор выполнения, пока не будет завершена вся загрузка. Есть 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

Пример запуска

  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

жду пример

Kushal
источник
1
Спасибо за упоминание, что launchэто для последовательных шуток, а asyncдля одновременных
Akbolat SSS
Вы использовали запуск один раз для всех задач и асинхронность для каждой. Может быть, это быстрее, потому что каждый был запущен в другой сопрограмме и не ждет кого-то? Это неверное сравнение. Обычно производительность одинакова. Одно ключевое отличие заключается в том, что при запуске всегда запускается новая сопрограмма, а не асинхронная, которая разделяет владельца. Еще один фактор заключается в том, что если одна из асинхронных задач не будет выполнена по какой-либо причине, родительская сопрограмма также не будет выполнена. Вот почему асинхронность не так популярна, как запуск.
p2lem8dev
1
Этот ответ неверен, сравнивая асинхронные функции с функциями приостановки напрямую вместо запуска. Вместо вызова функции приостановки непосредственно в примере, если вы вызовете launch (Dispatchers.IO) {downloadTask1 ()}, вы увидите, что оба выполняются одновременно, а не последовательно , вы не сможете получать выходные данные, но вы увидите, что это не последовательный. Также, если вы не объедините deferred.await () и не вызовете deferred.await () по отдельности, вы увидите, что асинхронность является последовательной.
Фракийский
2
-1 это просто неправильно. И то launchи другое asyncзапустит новые сопрограммы. Вы сравниваете одну сопрограмму без детей с одной сопрограммой с 3 детьми. Вы можете заменить каждый asyncвызов на, launchи абсолютно ничего не изменится в отношении параллелизма.
Мойра
Посторонний шум в этом ответе добавляет сложность, которая выходит за рамки сопутствующей темы.
правдаадюстр
6
  1. оба компилятора сопрограмм, а именно launch и async, в основном являются лямбдами с приемником типа CoroutineScope, что означает, что их внутренний блок скомпилирован как функция приостановки, следовательно, они оба работают в асинхронном режиме И оба будут выполнять свой блок последовательно.

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

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

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

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

AouledIssa
источник
1
Спасибо за этот комментарий. Здесь собраны все точки потока. Я бы добавил, что не все запуски отменены, например, Atomic нельзя отменить никогда.
p2lem8dev
4

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

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

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

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

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

Ключевая разница между асинхронностью и запуском. Отложенный возвращает конкретное значение типа T после того, как ваш Coroutine завершит выполнение, а Job - нет.

Прибыль
источник
0

Async vs Launch Async vs Launch Diff Image

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

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

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

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