Билдер launch()
Начнем с билдера launch. Мы уже использовали его в прошлых уроках, чтобы создавать корутины. Но у него есть еще пара возможностей, о которых надо рассказать отдельно.

В прошлом уроке мы рассматривали такой пример:

private fun onRun() {
   scope.launch {
       // ...
   }

   // ...
}

Мы выяснили, что билдер launch создает и запускает корутину. Билдер не ждет, пока корутина выполнится, и не блокирует выполнение метода onRun. Билдер быстро делает свое дело, метод onRun продолжает выполнять свой дальнейший код и запросто может завершиться раньше, чем корутина из него запущенная. Это стандартное поведение многопоточного кода.

Но иногда может возникнуть необходимость дождаться выполнения корутины. Для этого используется метод Job.join()

Схематично это выглядит так:

job = scope.launch {
   // ...
}

// ...

job.join()

Билдер launch создает и запускает корутину и возвращает ее job. Используя метод join мы приостанавливаем выполнение кода, пока корутина не завершит работу. Т.е. мы запускаем работу в новом потоке, а в своем потоке ждем, пока работа не завершится. Но тут есть нюанс. Обратите внимание, я сказал, что join приостанавливает, а не блокирует выполнение кода. Это suspend-функция. А значит ее надо вызывать внутри корутины.

scope.launch {

   job1 = launch {
       // ...
   }

   job2 = launch {
       // ...
   }

   job1.join()
   job2.join()
}

Вызываем launch внутри launch, тем самым создавая дочернюю корутину внутри родительской. В родительской корутине мы можем вызвать suspend-функцию join(), чтобы дождаться завершения работы дочерней корутины.

Теперь в точке вызова join родительская корутина будет ждать, пока не выполнится дочерняя. Напомню, что join — это suspend-функция, поэтому она только приостановит выполнение родительской корутины, но не заблокирует ее поток.

Билдер asynс()
Билдер async похож на launch. Он также создает и стартует корутину. Но если launch корутина делала свою работу и ничего не возвращала в ответ, то async корутина может вернуть результат своей работы.

Как вы понимаете, чтобы получить результат работы корутины, надо дождаться окончания ее работы. Это очень похоже на ранее рассмотренный нами метод join. В случае с async это будет метод await. Он не только дождется окончания работы корутины, но и вернет результат ее работы.

val deferred = scope.async() {
   // ...
   "async result"
}
val result = deferred.await()

Вместо Job мы получаем Deferred. Это наследник Job, поэтому имеет все те же методы. Но дополнительно у него есть методы для получения результата работы корутины. Один из них — метод await.

Все аналогично примеру с launch+join. Билдер async создает и запускает дочернюю корутину. Родительская корутина продолжает выполняться и останавливается на методе await. Теперь она в ожидании завершения дочерней корутины.

Дочерняя корутина выполняется в отдельном потоке. По ее завершению метод await возвращает результат ее работы (строка “async result”), и возобновляет выполнение кода родительской корутины.

Параллельная работа
С помощью async можно распараллеливать работу suspend функций.

private fun onRun() {
   scope.launch {
       log("parent coroutine, start")

       val data = async { getData() }
       val data2 = async { getData2() }

       log("parent coroutine, wait until children return result")
       val result = data.await() + data2.await()
       log("parent coroutine, children returned: $result")

       log("parent coroutine, end")
   }
}

Билдер withContext
withContext это coroutineScope с возможностью добавить/заменить элементы контекста.

Чаще всего используется для смены диспетчера:

aunch {
   // get data from network or database
   // ...

   withContext(Dispatchers.Main) {
       // show data on the screen
       // ...
   }

   // ...
}

В launch выполняется какой-либо тяжелый код для получения данных. Далее с помощью withContext переключаемся на Main-поток и отображаем данные на экране. После чего корутина продолжает выполняться в своем диспатчере.

Либо корутина может выполняться в Main-потоке, а с помощью withContext мы можем переключиться на IO-поток, чтобы сделать запрос на сервер или в БД, и получить результат обратно в Main-поток.

Билдер runBlocking
runBlocking запускает корутину, которая блокирует текущий поток, пока не завершит свою работу (и пока не дождется завершения дочерних корутин).

В Android разработке такая корутина нужна для написания unit тестов, чтобы удержать поток, в котором выполняется тест. Иначе тест просто не дождется завершения выполнения вызываемых корутин и завершится. Подробнее об этом мы еще поговорим в уроке про тестирование.

Если в runBlocking произойдет ошибка, то все его дочерние корутины поотменяются, а сам он выбросит исключение.

Также он может вернуть результат своей работы.