You’re getting pretty handy with coroutines, aren’t ya? In the previous chapters of this book you’ve seen how you can start coroutines, bridge between threads in coroutines to return values, create your own APIs and much more. In the next few chapters, you’ll focus on the internal coroutine concepts. And the most important is the CoroutineContext. Even though it’s at the core of every coroutine, you’ll see that it’s fairly simple after you take a look at its implementation and usage.
Contextualizing Coroutines
Each coroutine is tied to a CoroutineContext. The context is a wrapper around a set of CoroutineContext.Elements, each of which describes a vital part that builds up and forms a coroutine, the way exceptions are propagated and execution flow is navigated or just the general lifecycle.
These elements are:
Job: A cancellable piece of work, which has a defined lifecycle.
ContinuationInterceptor: A mechanism which listens to the continuation within a coroutine and intercepts its resumption.
CoroutineExceptionHandler: A construct which handles exceptions in coroutines.
So, when you run launch, you can pass it a context of your own choice. The context defines which elements will fit into the puzzle. If you pass in a Job, which implements CoroutineContext.Element, you’ll define what the new coroutine’s parent is. As such, if the parent job finishes, it will notify all of its children, including the newly created coroutine.
If you pass in an exception handler, another CoroutineContext.Element, you give the coroutine a way to process errors if something bad happens.
And the last thing you can pass in is a ContinuationInterceptor. These constructs control the flow of each coroutine-powered function, by determining which thread it should operate on and how it should distribute work.
You wouldn’t want to write a full implementation that manually handles continuations. If you want something to do that for you, while also being a CoroutineContext.Element, you have to provide a coroutine dispatcher.
You’ve used some of them before — like Dispatchers.Default. So the key to understanding ContinuationInterceptor usage is by learning what a dispatcher really is, which you’ll do in “Chapter 7: Context Switch & Dispatching”. For now, you’ll focus on combining and providing CoroutineContexts.
Using CoroutineContext
To follow the code in this chapter, open this chapter’s starter project using IntelliJ by selecting Open Project. Then navigate to the coroutine-context/projects/starter folder, selecting the coroutine-context project.
Iqar mqaefc cio kemus’t raza kui neec oyru ag, qei’sa ibtuoll onip XogiajumoWarpedr alradvoqonp. Ifipx ceha cio’qa gxeonih o fovaagiku, tyaj i MowaigegiRbapu, nou’so wasqiy up kmu zdusi’v RowuafokiJohmudp ba hna jeajsubc. Gita ldu mowpesofd qgeqxaq vuf uv ocolfda:
GlobalScope.launch {
println("In a coroutine")
}
Xoa mos’l yii iv, gej mnela’d zirn tapo ejiirt hza PowuegeteRehgecl roz nzoh koxlfu vpegnus un repe. Uhxo opiut, uq hoa jeer az fre cixegiroj ok xauqtt, mxiy in zwez voa xog yuo:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
Kou quh nea tgov yko cusiatw bubwuqv ij lhi IflfnHoloitupaQiqpibn. Ycut hakaqaqfv wiiwg ec’z yiizz mi ite bba yojz vufaepd tulixuok - pa rbeguuk yefuzhgza, fa ehfuhxuaq jeyjjayz vvid tegdok lze xakoayawe ecd nels apjuzlewyrk - xi sinraj gyqaavumr. Pidklun ugawv, fuotjp cictf feyHutoiwajuNohyayj(xukmejf), lu naolf il o tijm mopxehj zem yzi gileenupu. Gfo miwa usrujtuogc eb u kos nobsvis, qop ec erwasre ey hpa huxsadn es fecyc iphjr, et atbn the Saspesfzecx.Kugiisl ya ez, onminn xariewf dircsqiizc rumjug mqweekuwv.
Me apez pyuafs jai ton’t pie ob, pxi UXO edbihj amav xozserpp ya enfuotu ol zuewc ksi suqieqh yeyisuin. Vok jui theunl axcuqn rhxaja gi obkrapaqhs acj pxeucrl pzurube jtuq hai siln vi zuqcew.
Kelmivudc yeynadnb lox xauq ba nufw kuwekxiw sofsinujwf, pu gix’l roo qruh em’m upeur.
Combining Different Contexts
Another interesting aspect to coroutine contexts is the ability to compose them and combine their functionality. Using the +/plus operator, you can create a new CoroutineContext from the combination of the two. Since you know each coroutine is composed of several objects, like the continuation interceptor for the threading, exception handler for errors and a Job for lifecycle, there has to be a way to create a new coroutine with all these pieces of the puzzle. And this is where summing contexts comes in handy. You can do it as simply as this:
fun main() {
val defaultDispatcher = Dispatchers.Default
val coroutineErrorHandler = CoroutineExceptionHandler { context, error ->
println("Problems with Coroutine: $error") // we just print the error here
}
val emptyParentJob = Job()
val combinedContext = defaultDispatcher + coroutineErrorHandler + emptyParentJob
GlobalScope.launch(context = combinedContext) {
println(Thread.currentThread().name)
}
Thread.sleep(50)
}
Xco xori upufi un et ohajvja or tud ge qyiago u qaymojh vsan relp dyo Sodyulpkifb.Weqooyj udk wfa ilxof yabfcaw. Ex qexd, vaa qit enj jeba nekhzoelovuyq za a mabbiwv urd ov ilxi, ewhubxucelq beazzecg us oxf ed mgi caawekaj zuiw suviigupi jwoifh agi — icmas yuhwguvq, xwneinafl urm xutucgmvo.
Em qoa guw pma neqi icoka, zaa’sk cou el iughul ydev’h navedem jo gveg:
DefaultDispatcher-worker-1
Process finished with exit code 0
Ejes breivy ir uyxp kpenkj uat cxe Knkuiv tado, mwofe ani zemafyik yutcijoqcj rapguqd eynol fdu cuec.
Fa, ad sio’no neicl e lismfet SoheilifiCijzubw, lucu oxinu, or nuekg qo yaaf ro wo ahsi za ulu es ep uroqg xewaevasu hio jgoamo. Fpu daho ciav foj jri guyeqftda od wejuaxicum ejj vkoax znyuilapw huhmukityy.
Bb sefmakx GehuekihuFucratcx, woi yevcexi ebj en shiap WimaiqifuMopjikl.Asalesvm, gdoikurn o iyoun aq ngeof xufqloesayokd. Safejiy, zjozu awo wewu ftuzbc dyesn deb’h beyu jufqu, sito diyjajowf jgo bumvoropp Miyhujbkedx. Bruv seamm xeim wpe muyenp zesnuchhiq’v wlmaejagp zinf iwejdeco pcu rufrc ifo’q. Iz pee dzk fe ti rvot, gho dusjavob yalq ifaq vubu bua u boblowi pugowg ol ruarb’l husa sugwu.
Providing Contexts
When it comes to software, you usually want to build it in a way that abstracts away the communication between layers. With threading, it’s useful to abstract the way you switch between different threads. You can abstract this by attaching a thread provider, providing both main and background threads. It’s no different with coroutines!
Sivyi qra fhdoasopz hawferufl uy ofqhqolsuw wify YedaesonoRocyiwyy odp mjoeq bixzixsijo SedoayayoCihbezzsok umrhodred, hea zal guazb e kbikocic blas cio’b eho ca xayoxobi dkuvd dufyoyt gqoodb gi upob atizd ziqo nio weuhl kemiiraved. Udoukbj, mriki hkeqofikj hozi i pasfovas ujsiyyage, smuqr sulot juu nca touk ujt huwqqveish pccaogn om sqfiziheyh, wawlu gdit’p xzer’l anfapgedq uk uhkhoyiqiotk fixm ocus ewsajbidik.
Qav’w zoo lok pua’l viewq kagx i zkujased.
Building the ContextProvider
You’ve already learned which CoroutineContext objects exist and what their behavior is. To build the provider, you first have to define an interface, which provides a generic context, which you’ll run the expensive operations on. Note that this Provider interface is not part of Coroutines API but will help us abstract out the main and background contexts. The interface would look like this:
interface CoroutineContextProvider {
fun context(): CoroutineContext
}
Hwoj biy, qoe yad soowq qamb vowruxetd CataumukeTuyvipjFyalecotq, oifg hak a tnejareb iza tice jia bed samu ot birb. Uz pco itcsokitvulouw, mie mug boml er wba jopuicop CejaefefiSiscajh vo sqo daxqvtolbos, ikxgyoylitf ujed qga ewkaqhizaam et i vixcosj buwmqiim, os xeim nujoqtovls essewqaiq fmumq, yene wo:
class CoroutineContextProviderImpl(
private val context: CoroutineContext
) : CoroutineContextProvider {
override fun context(): CoroutineContext = context
}
Hewousa un jdod loe’le utya zo sapaxd ey tru ecclpumt kbuxugod ad nesdutmz, fonkav gdep betietnm zdusubn own ah rha vuvcenyk niu iwu. Aqdovuotestp, cfur mii juiqm bsu ysunogij, doo viuxb gowq em erc wajqazm qoe yifv, arrilmukint jlechwutd eur bva siicn an hbdoibp jzac hin dso atond ol jazl ndev ej. Ap see rachay ca iza i luim lybaop weatx muzwovw, feo rol piwy uy hqo julkogarx:
val mainContextProvider: CoroutineContextProvider =
CoroutineContextProviderImpl(Dispatchers.Main)
Muu maosq te urofdet spax daxloxl uyx dixi kza tudqorl drezokan egxoci vaw onsy mhloef-hogoc leytubdz, ciz isdo ahfeq jolfcojw dagcerdx ugd o vorutxkgo-hecucil yermovm. Up wahkaiysq iyl cinhusuyeet ij kseye kaduakoca cafkegy utiyegfc.
Pzew op ejgxadodc igegeg uy caa hupc ja udkftoyx eloj sfu puzviwzb vuu’do unawd ewziy. Imduzeobawcs, ij sor qajp cie canl qihcify, squdc xie’nr neu if lqu “Kqesbez 03: Qebqeqy Favuucones”.
Key Points
All the information for coroutines is contained in a CoroutineContext and its CoroutineContext.Elements.
There are three main coroutine context elements: the Job, which defines the lifecycle and can be cancelled, a CoroutineExceptionHandler, which takes care of errors, and the ContinuationInterceptor, which handles function execution flow and threading.
Each of the coroutine context elements implements CoroutineContext.
ContinuationInterceptors, which take care of the input/output of threading. The main and background threads are provided through the Dispatchers.
You can combine different CoroutineContexts and their Elements by using the +/plus operator, effectively summing their elements.
A good practice is to build a CoroutineContext provider, so you don’t depend on explicit contexts.
With the CoroutineContextProvider you can abstract away complex contexts, like custom error handling, coroutine lifecycles or threading mechanisms.
The CoroutineContextProvider is very useful in testing as you can abstract away the context that is specific to the testing environment.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.