Для выполнения каждой Корутины нужен CoroutineContext. А если точнее CoroutineContext-ы). А если еще точнее то компоненты наследники от класса CoroutineContext. Это необходимые компоненты, которые нужны для контроля над корутиной, переключения потоков выполнения, обработки ошибок и т.д. Но где и куда его вставлять? =) В предыдущих примерах мы уже встраивали и использовали CoroutineContext. Помните Job-у, которую мы вписывали в контруктор scope-а? Ну так вот Job это производная от интерфейса CoroutineContext.
Производными от CoroutineContext являются также CoroutineDispatcher, CoroutineExceptionHandler.
CoroutineContext имеет «под капотом» реализацию своеобразной типизированной Map-ы. Он хранит в себе элементы(интерфейс CoroutineContext.Element), и их можно достать по ключу. Это означает, что мы можем комбинировать разные Job-ы, Dispatcher-ы в один объект и вставлять в scope! Давайте посмотрим как это сделать:
val myContext = Job() + Dispatchers.Default + CoroutineExceptionHandler { coroutineContext, throwable -> }
val myScope = CoroutineScope(myContext)
или
val myScope = CoroutineScope(Job() + Dispatchers.Default + CoroutineExceptionHandler { coroutineContext, throwable -> })
Если помните, в прошлых уроках мы создавали scope следующим образом:
private val scope = CoroutineScope(Job())
Вместо Context мы передавали Job. Это работает, т.к. любой элемент, который можно поместить в Context, сам по себе также является Context-ом. Т.е. Job — это просто Context с одним элементом.
Аналогично и Dispatchers.Default — это Context с одним элементом. Можно сделать так:
val scope = CoroutineScope(Dispatchers.Default)
Scope поймет, что ему передают Context с одним элементом — диспетчером.
А этот код трехэлментного CoroutineContext:
val myContext = Job() + Dispatchers.Default + CoroutineExceptionHandler { coroutineContext, throwable -> }
Job для scope будет создан автоматически, если его нет в контексте.
Когда мы создаем scope и передаем ему Context, выполняется проверка, что этот Context содержит Job. И если не содержит, то Job будет создан.
Передача данных Context—а при создании корутин
В уроке про scope мы выяснили, что каждая корутина сама по себе является scope. Это необходимо, чтобы создавать дочерние корутины. Если быть более конкретным, то Job корутины является по совместительству ее scope-ом, а следовательно и содержит контекст корутины. Давайте разберемся, как этот контекст наполняется элементами.
Когда билдер создает новую корутину, он создает для нее новый пустой Context и помещает туда элементы из контекста родителя этой корутины. Т.е. элементы контекста scope будут переданы в контекст корутины, созданной в этом scope. Аналогично элементы контекста родительской корутины будут переданы в контекст ее дочерней корутины.
Но есть пара нюансов.
Во-первых, Job не передается. Для создаваемой корутины создается новый Job, который подписывается на Job родителя и помещается в контекст созданной корутины.
Во-вторых, если при передаче выясняется, что отсутствует диспетчер, то будет взят диспетчер по умолчанию. Поэтому мы можем нигде явно не указывать диспетчер. В этом случае корутина сама возьмет себе дефолтный.