В чем разница между запуском / объединением и асинхронным / ожиданием в сопрограммах Kotlin
В kotlinx.coroutines
Библиотека, вы можете начать новую сопрограмму, используя либо launch
(с join
) или же async
(с await
). В чем разница между ними?
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
используются для запуска новых сопрограмм. Но исполняют их по-разному.
Я хотел бы показать очень простой пример, который поможет вам очень легко понять разницу.
- запускать
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
- асинхронный
Как мы видели, 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
оба конструктора сопрограмм, а именно запуск и асинхронность, в основном являются лямбда-выражениями с приемником типа CoroutineScope, что означает, что их внутренний блок компилируется как функция приостановки, поэтому они оба работают в асинхронном режиме, И они оба будут выполнять свой блок последовательно.
Разница между запуском и асинхронным запуском заключается в том, что они предоставляют две разные возможности. Конструктор запуска возвращает задание, однако асинхронная функция возвращает отложенный объект. Вы можете использовать запуск для выполнения блока, который вы не ожидаете от него какого-либо возвращаемого значения, т.е. записи в базу данных или сохранения файла, или обработки чего-то, что в основном вызвано его побочным эффектом. С другой стороны, async, который возвращает Deferred, как я уже говорил ранее, возвращает полезное значение из выполнения своего блока, объекта, который обертывает ваши данные, поэтому вы можете использовать его в основном для его результата, но, возможно, и для его побочного эффекта. NB: вы можете удалить отложенное значение и получить его значение с помощью функции await, которая заблокирует выполнение ваших операторов до тех пор, пока не будет возвращено значение или не возникнет исключение!Вы можете добиться того же самого с запуском, используя функцию join()
оба конструктора сопрограмм (запуск и асинхронность) могут быть отменены.
что-нибудь еще?: да, с запуском, если в его блоке возникает исключение, сопрограмма автоматически отменяется и исключения доставляются. С другой стороны, если это происходит с async, исключение не распространяется дальше и должно быть перехвачено / обработано в возвращенном отложенном объекте.
подробнее о сопрограммах 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
завершено.