Сопрограммы позволяют писать асинхронный код более последовательным и читаемым образом, упрощая работу с фоновыми задачами, такими как сетевые запросы, операции с базами данных и длительные вычисления, без блокировки основного потока (UI).

Конструкторы сопрограмм

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

1. запуск: этот конструктор идеально подходит для запуска новой сопрограммы, которая не возвращает результат. Он идеально подходит для задач «выстрелил и забыл», таких как выполнение фоновых операций или обновление пользовательского интерфейса.

CoroutineScope(Dispatchers.IO).launch {
    // Perform a background task
}

2. async: используйте async для запуска сопрограммы, которая возвращает значение Deferred, что позволяет вам получить результат асинхронной операции без блокировки с помощью await().

val deferredResult = CoroutineScope(Dispatchers.IO).async {
    // Perform an asynchronous task and return a result
}
val result = deferredResult.await()

3. runBlocking: хотя runBlocking обычно используется в модульных тестах и ​​основных функциях, его лучше избегать в коде пользовательского интерфейса Android, поскольку он может заблокировать поток пользовательского интерфейса.

runBlocking {
    // Perform blocking operations
}

4. withContext: этот конструктор временно переключает контекст (поток) сопрограммы для выполнения определенной задачи перед возвратом в исходный контекст. Это полезно для работы в другом потоке, а затем возврата в поток пользовательского интерфейса.

val result = withContext(Dispatchers.IO) {
    // Perform an operation on a background thread
    "Result"
}

Области сопрограммы

Области сопрограмм — это фундаментальная концепция сопрограмм Kotlin, которая помогает управлять жизненным циклом и отменой сопрограмм. Они предоставляют структурированный способ запуска сопрограмм и управления ими в определенной области, гарантируя, что все сопрограммы, запущенные в этой области, автоматически отменяются при отмене области. Это особенно важно для предотвращения утечек памяти и обеспечения правильной очистки фоновой работы.

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

GlobalScope.launch {
 // Coroutine code in the global scope
}

2. CoroutineScope: это настраиваемая область сопрограммы, которую вы можете создать для управления жизненным циклом сопрограмм в определенном контексте, например жизненном цикле компонента Android (например, активности или фрагмента) или настраиваемом контексте.

val coroutineScope = CoroutineScope(Dispatchers.IO)
coroutineScope.launch {
 // Coroutine code within the custom scope
}

Когда контекст-владелец (например, активность или фрагмент) уничтожается, вы можете отменить CoroutineScope, и все связанные с ним сопрограммы также будут отменены.

3. viewModelScope: при разработке приложений для Android вы можете использовать viewModelScope, предоставляемый библиотеками AndroidX, такими как androidx.lifecycle.viewModelScope. Он привязан к жизненному циклу ViewModel и автоматически отменяет сопрограммы при очистке ViewModel.

class MyViewModel : ViewModel() {
 fun performTask() {
 viewModelScope.launch {
 // Coroutine code within the viewModelScope
    }
  }
}

Когда ViewModel больше не нужен, его viewModelScope автоматически отменяется.

4. lifecycleScope: это еще одна область действия, специфичная для Android, предоставляемая библиотеками AndroidX, такими как androidx.lifecycle.lifecycleScope. Он привязан к жизненному циклу компонента Android (например, активности или фрагмента) и отменяет сопрограммы при уничтожении компонента.

class MyFragment : Fragment() {
 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 super.onViewCreated(view, savedInstanceState)
 
    lifecycleScope.launch {
       // Coroutine code within the lifecycleScope
     }
   }
}

Когда компонент Android (например, фрагмент) уничтожается, его жизненный цикл автоматически отменяется.

5. SupervisorScope: особенно полезен, когда вы хотите изолировать и обрабатывать ошибки таким образом, чтобы не нарушать всю иерархию сопрограмм. Это обеспечивает более надежную и детальную обработку ошибок в ваших сопрограммах.
Вот пример использования SupervisorScope:

fun main() = runBlocking {
    supervisorScope {
        val child1 = launch {
            try {
                // Perform a task that might throw an exception
                delay(100)
                throw RuntimeException("Child 1 failed")
            } catch (e: Exception) {
                println("Child 1: Caught an exception: ${e.message}")
            }
        }
        
        val child2 = launch {
            // Another child coroutine
            delay(200)
            println("Child 2: Completed successfully")
        }
        
        // Wait for child coroutines to finish
        child1.join()
        child2.join()
    }
    
    println("Parent: Completed")
}

В этом примере:

Мы создаем область супервизора для запуска двух дочерних сопрограмм, child1 и child2.
child1 намеренно генерирует исключение, но мы перехватываем его внутри сопрограммы и обрабатываем, не затрагивая другие сопрограммы.
child2 завершается успешно, и его результат на него не влияет сбой дочерней программы1.
Родительская сопрограмма, содержащая супервизор, печатает «Родитель: завершено» после завершения дочерней сопрограммы.

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

Диспетчеры сопрограмм в Котлине — это объекты, которые контролируют запуск сопрограмм. Сопрограммы могут выполняться в основном потоке, фоновом потоке или в пользовательском пуле потоков.

  1. Dispatchers.Main: этот диспетчер запускает сопрограммы в основном потоке.
  2. Dispatchers.IO: этот диспетчер запускает сопрограммы в фоновом потоке, оптимизированном для операций ввода-вывода.
  3. Dispatchers.Default: этот диспетчер запускает сопрограммы в фоновом потоке, оптимизированном для операций с интенсивным использованием ЦП.
  4. Dispatchers.Unconfined: этот диспетчер запускает сопрограммы в потоке, который их создал.
  5. Dispatchers.UnboundedPool: этот диспетчер запускает сопрограммы в пуле потоков, который может увеличиваться и уменьшаться по мере необходимости.

Некоторые полезные функции сопрограмм

  1. suspendCoroutineФункция suspendCoroutine полезна для написания асинхронного кода, выполнение которого можно прервать и возобновить позже. Например, вы можете использовать suspendCoroutine для написания функции, которая вызывает API, а затем возобновляет работу сопрограммы при получении ответа.
suspend fun fetchData(): String = suspendCoroutine { continuation ->
    // Simulate an asynchronous operation with a callback
    fetchDataFromNetwork { result, error ->
        if (error != null) {
            // If there's an error, resume with an exception
            continuation.resumeWithException(error)
        } else {
            // If successful, resume with the result
            continuation.resume(result)
        }
    }
}
  1. withTimeoutOrNull: функция withTimeoutOrNull в Kotlin — это функция приостановки, которая запускает сопрограмму с тайм-аутом. Если сопрограмма не завершается до истечения времени ожидания, функция withTimeoutOrNull возвращает значение null.
fun main(args: Array<String>) {

    // Create a coroutine scope.
    withContext(Dispatchers.Main) {

        // Run a coroutine with a timeout of 1 second.
        val result = withTimeoutOrNull(1000L) {
            // Do some work that takes more than 1 second.
            delay(2000)
            "Hello, world!"
        }

        // Check the result of the coroutine.
        if (result != null) {
            println(result)
        } else {
            println("The coroutine timed out.")
        }
    }
}

Удачной сопрограммы!!!