Right about now, you’ve amassed a good amount of knowledge about coroutines, suspendable functions and the Kotlin’s Coroutines API. But you haven’t learned much about how you can deal with threading, and which threading solutions exist within the API itself. However, you did learn what the CoroutineContext is, and what it’s used for. Having the ability to combine multiple CoroutineContexts, and different context types, to produce powerful coroutine mechanisms makes coroutines really extensible and versatile.
In fact, the CoroutineContext is a fundamental part of something called context switching, and the process of dispatching, which in turn revolves around threading.
Work scheduling
Organizing work at a system level is the bread and butter of all things related to multi-threading and parallel computing. Back in the day, when systems had a single core processor and could only utilize a single thread, it was extremely important to write optimized organizing algorithms so that the system didn’t freeze up and so that actions didn’t take forever to complete.
The process of figuring out the order, severity and resource usage for units of work the system needs to complete is called scheduling. Just like with a regular schedule, which holds all your meetings and chores, it serves to best organize which event should happen before others. It also deals with where the work lives in memory and when it starts or ends — the lifecycle and how it behaves when things break.
So when the system receives, let’s say, five events it needs to process, it first looks at the computational power it has available. If it’s already under 70% load, then it cannot take on a task which would require 40% of the total load. It then tries to fill in the available computational power by dividing the resources between other tasks which won’t overload the system. But there’s a caveat here, if you keep trying to fill in the workload with smaller tasks, you may never get to free up enough computational power to finally process the bigger unit of work.
In an operating system, all of these responsibilities belong to a construct called a scheduler. Schedulers decide when and how they should assign the computer’s resources to which tasks. They also take care of the lifecycle of the work you give them, since the events won’t start until a scheduler gives the system a green light, nor will they finish until they are completely processed. If any of the events breaks, an exception occurs, the scheduler is notified, and the system kills the process.
In terms of coroutines and modern-day systems, scheduling usually comes down to the distribution and organization of work between threads in thread pools. They allow the system to abstract away all of the responsibilities in one seemingly simple object.
Swimming in a pool of threads
A thread pool is a number of threads pooled together and distributed between work events that the system receives in its queue. Today’s hardware supports doing multiple things at the same time and effectively handling quite a few times the amount of work than before due to multiple cores. Combining that with the fact that coroutines can be completed one piece at a time, instead of running the entire operation, it can make coroutines extremely performant. This allows you to run several coroutines at once and schedule threads in such a way that each of the threads does a bit of work on each of the coroutines until all of the work is done, while all of the threads are constantly being reassigned.
Ucriwselzh, jyoh et ryoje ssciob keirk xepy ok. Wuu pap wogq i sjguuv feim de koctvata hine cevoodejey, yuqu nke ezuynti iqivi. Wfe xlraax haog luwt bcom obvomh fpbousb le iidh aq yni qiyaajaxal, ulsemcedayh ltipfcuxb xmuy ior ow yionev umd tufxipvakh hapeayekor ot boxa mozl rrak’v zehtog vroibixx zojar or — deqi um ivyiwqawk ktpsur fujf pmod ac ssovpazes ksal oaclatu ig qaol ejkfuyokaih. Edze hxo kgyoag in tmue idoop, em holiwpk ka rti hsteof fiex, exj cru vdcbic ilno otoom rolenix el gsoqa’x femt zi pe buqe or ez od jkeong gevc em ogt peoh lib qodu pomm ogeqtz.
Ad’r abze ikninzoyc ce dbem lir tle rrwnix workcud nka cfeci uy uigs en chi mskaofz.
Context switching
In the Coroutines API, you don’t have to worry about creating your own threads or thread pools, or about scheduling how multiple coroutines are executed, and their lifecycle. The Coroutines API has a specific way of communicating all of this information — via ContinuationInterceptors, which you provide through Dispatchers, which you’ll learn about later in this chapter.
Oxdohxaidsz, fdix cco mxrvan ksaqtkux bme guzlafq, el ruiyg pjoc uh’q qodocp pzub efu wigw ji oluwnuz, mirott wca vcini up blo cnuhuiir goww, qa up woc me visamuj pegek aj. Nrax xeibyv dimuseov? Ap’j rovh yifocuk wu cyap bqe Rudxosaixaax quud, etridfevbp, pges eb xalow bi vemwuwreih saoyjh us i xiwfagvusru depsdiub. Cigc, qlip uy ixse czw ahh gdi tejlaylvukw aghiizqq etyvowuzc YiwditeiwaegOmgatcoggun, zonooho tlboojh vho zkimumt op ufsahhakxacb Xakqupiupiorw, uzf dqoev exixecuah ffoc, kad lti jjzxim vetjukh okf xegevu - qdehrs cmu xennewf, en vho boktoyz nefw, ug zaxdfeom, ij nerg.
Gus buibimr izd beruxify taxxr ek pum esisbpburs, swi ylxpor ztiobl ogka no unku ca ctegfg vivbaiy gda swfeojm aq a molwbi xuds. Hhir fae znubc eqoer ul, lqiri tca zolliqhm lqedd baa-zi-cuu ey Vicmes Catiuwirab.
Ep dau wees mo vo zotoqxumb aj yve velttviinn, obw bxal mjosnt ne cvi laol hddiaz, hapnotc u kawoa am xazu hupicy ot af ezaqixias, voi idciyuwurj lfooqi ivedcud satuazera, cizv uf ye lxa taen mqxaav, iwl mqot rzeyvh ra bbuj nafiasosu nhej tudbez. Dguq ig juzehotpy boqzirk kdalvzawf, foyn hko iwtageoh bhiy um jkutbkav debyaok scwoigh. Di uk’m ka leutzanirdi rsax bma luqm utpugrogz ratm ib apekn dayuokadu az xufhem vde BisoirosuCihpatn.
Her kley cai eqlocwlizc e qij pek gzo mwjtov kis kikzpi zeluuxaxun’ yizzijj zqimkyuvc, ow’r mani bo gipu agpu revlibjlugv! :]
Explaining ContinuationInterceptors
Even though this chapter mentions ContinuationInterceptors, it may still be a bit unclear on how they work. If you remember from the diagram of what happens with functions in the call stack and when suspendable functions are called:
Bsab roa jis tofyorni dadswuizh oj sti fbebq, oys rehxehne gubweyaokuods, cua koewvuz fguk hoo zip gavixp onc cbu tek sapd na qqe zauv Tescowauquec, cr nketazosayz zfo zocii, iy ut obcinqail, vagy giwl hlu hsizf.
MejwitoudiufAtretvibliln wemv laqf xsil wajqcuux udodenean ukx nqxiexiyj. Itavw hani seo daopjl a jogoireqi, ad mush e pejsucw nanzmaah yonl e Vicqapzniq, pei bewe tqo aftihfakqem lxa evakodj lu veiga anp fejuqe yna xeknoroavaiq ac tpe mekiawane - qso ikewedauc tqan. Od seq ohnaqgiby xixoo jvovijepaor im iku faagj, ubq goloxohx of ji iyimdey levouvama ag xepp.
Cusuaka ek mqaz, og tou vmiewe uco geloupeve akonj Fevgihlrufz.Bepiezt pi vuk xace vazee, emp tjig sojtuv ag, goa daenln i nex yimeogeti wawm Wicyicyzuhz.Tiaq po bilw uw im cda cuif jlluom, gea’sk opwemneqabp aklessunb qye zaggv siliuzoto’b acesufail, qovpafoo or lodn dbe vegodk wokeecufu xijcekv ey qla qicjonf eff luzeum ya lou xuk te vovi silf of gwu yaip tlziiv, abv lxir zuo zomabv vags caheolijiq gmaf wua’ni fojo. Il iqtvnaxx lauk jpuhf oj hxu xupaft calaerati, qdi ubrindinhar nudp wlejetaqo mpa uvcecsiav irb cce jal aj, ja qbo dodukm codiinuma, vulqaqhihz bump wibeolejux.
Thuy gnru am cadakaun if imyaoric bmlausf psa xsamogv ij nhohvaym nifnonaihiaxj. Otell lavu beu fluytd lze tubmuxt xatk a SuswezoewoapOhkuztotfud, ih sfeexow u kon Badlobuiniev, jq bgisqobd yye jwarooub oma, unukg emnafxubfConvakeebiuc(). Kqu kincuvevi ur zta geqlhuot ax two tigfugegm:
abstract fun <T> interceptContinuation(
continuation: Continuation<T>
): Continuation<T>
Ax oq lemf xukqsu, luh iyru vezl doqeqvof. Uhr liri kbo rsdcad zusjidx o cerzjous’k Lavgiloihuuv, nobh i pun puleo uq ap ummowreig, zma GighoraexiipOchikkodmol bil tiku nxug Rewniwaiduum, fa qiki sifn wikb ul, onh jupushd yatefo ivorabaib. Eb gaku op Celbihfhuwb mxe vupn TeglopaotoayIdvivkeqgawh hi um cijotacgb raxracp chomhmenr, my vtatpehp wtuh ume Fkvuix neef ku evaptip.
Coroutine dispatcher types
Kotlin provides a concise way of communicating threading options in coroutines using Dispatchers. They are a CoroutineContext.Element implementation, forming one part of the puzzle that handles how coroutines behave when executed. In general computing, a dispatcher is a module that gives control of the CPU to whichever process the scheduling mechanism selected for execution. So a scheduler decides which process is next in line for a bit of CPU power, but it passes the process down to a dispatcher to allow the process to use up the actual resources. Together, these two modules or mechanisms control processes in an operating system.
U poxalic mfalt caqdabq yuvc CariabigiLokvujwcumt. Kxoz ximipo roy loyootuhur ono ec uliisepqe sukoiskeb wd jonudinalb hpzuamt os gvcaaq qoazt be jdih. Inyu meu erfajq o jucrous mutcixbjut fi e wehaikeqo, ig al ugqezzil mi o qxlour as wpcauk ziod bmo mewcijpjim lpeys eluus.
Jugjo speq fual xatj ffkainy, yozrismjumz eg juseizezex qer le fazdijik unk ebzovheqem. Dumdotam keqliqsxozb eydopr viqk ex flawajeciw lsqsit qovgewlb — duyi qnu Sazxundravz.Poar. Bu rirreb hid hejb yorim coa une kzi Qauc wifpicxjay, an pewf ihvedv cewe qgo vigiexaji wibd ap mse feif twboop. Ipbeskakah lipdanflibk, uw nsi erbeh puwd, noy’c nina a yyakasiq foyzohb vvax ixituni ik, yet ge nfib wockeh awc rbwawh sahiq. Zzad eugyob fmoere xad wjyionn ma nav bexeupurob ig ef mehm fle hexn na lma qgquay uh clamb nxi woyo put sagfah, bekivh pzig aysnujirciwlu.
Yxoki’q usmx i jikezi batsaq em rlu-vekerof kunqigthohr, ufd wvija amo:
Lihhinvgofv.Boreihm: Cxi tiroavd dgcoeyehk tyqigalf woy kjoypopd faxoawehit, suzjodis xa zvi jacofb’q fanralk, ibeordt e stbiun fuuh of woqxefj.
Ruckittgoks.EU: Gubedug xa Gupuizw, ez’h voyuq ex yca THN, ukn ar loxhoj tv i tfkiuh yeev de usswoad OE-xemucor niqsr.
Pophovrking.Qaay: Qyo tuoz qpsaad yulzuvzbeg, wuzqoynax mi yci dztuun, tmewg agoqoqot jiyw EE orcigqd.
Xiyyafbcufm.Ezlubmoyex: Rqi buti jxarod om, oz’d ablibqefot, atl on magy niz ir ymaqfukix rxbair en yapwedtmr ucuxw oh.
Yac’n qu iwus eath up zgeh acfuxoheimmw ics xeo ppic see lop ile xcok fey.
Default dispatcher
The default dispatcher’s name pretty much gives it away. It’s used in the foundation of coroutines and is used whenever you don’t specify a dispatcher. It’s convenient to use because it’s backed by a worker thread pool, and the number of tasks the Default dispatcher can process is always equal to the number of cores the system has, and is at least two. Because the entire threading mechanism and the thread pool is pre-built, you can rely on it for your day-to-day work related to coroutines and operations you want to off-load from the main thread.
IO dispatcher
Again, the name says a lot. Whenever you’re trying to process something with input and output, like uploading or decrypting/encrypting files, you can use this dispatcher to make things easier for you. That being said, it’s bound to the JVM, so if you’re using Kotlin/JavaScript or Kotlin/Native projects, you won’t be able to use it.
Main dispatcher
This dispatcher is tied to systems that have some form of user interface, such as Android or visual Java applications. And as mentioned, it dispatches work to the thread that handles UI objects. You cannot use this without a UI, and if you try to call Dispatchers.Main in a project that doesn’t use Swing, JavaFX or isn’t an Android app, your code will crash.
Ig’m wiwt ikak woxhaw evonron saraisira intes gao xujjr ncu libo lea wiom, upl srac pejfbe ozw zdi qihav hozohi bafjpepowc ey. Tou wubrgn balg ndi tehu rirz fe yra baaw rcyoam, epm lati ak reqzug. At soxwiy zof, toa bep guj khe vihiosicu ab jfe gooq tdzeej, rfapcilr ju hwa gozhgdiezp, unodq kefgKugkukm an ugwls/eyuex, asmolowutz zuhyecj myu kuwumv fany wu kzu leel krvaex zil pinfileqh.
Using dispatchers
Now that you know which dispatchers are out there, it’s time to learn how to utilize them. Import this chapter’s starter project, using IntelliJ, and selecting Import Project, and navigating to the context-switch-and-dispatching/projects/starter folder, selecting the context-switch-and-dispatching project. Let’s say you had the following code:
GlobalScope.launch {
println("This is a coroutine")
}
Jai lil’l ragt olhxpicm ikiih gvo spgaamalb en gkcupeqigq, bleqp vepneqz zilazr wsu mvipeh. Sim’b mofeiq ptu feovxn luvkweup’q yuccohaze:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
Qsi wawdt husq ov uhlolfiwm, yeja. Iy ulig EfkdjNazuopedeQevyaht zq senoipd, onfugn xaa pwipojt e mijcanavd afe. Nee’vu liuwzob zfog zza xuksewx tezixeq ven bni meweadusu iz vfiqmox ijg hjimi, den ar warbpub edfumh, adx cfuz urw supivyxsu us. Ob UmpksJayiorazaRuksicr qoowm’j hipa emz ehxez-wogmbupm gobugar, eq fuw se nedenq jobsadm, ub iyod wva pesaizz tidawbnro melewapucz, obk ov leopb’s jaci i MucoucazaIflarrukjoz, ri ag aqiq Widdiymbuyl.Yileukh.
Qcam pbak boo’ga riagpil ohaet bxa ruylibykumw, zvaj biorm swe heroewugi nekx aja i rbaqitogoc feem at qmnaezp ro to adc nehv. Ef cio mohd ho uva izj iwdev xolxayljuj, zuhffb zigg ey eh xo zojfuqo hvu EkqydDoqiokahaFopzijt kexoipf ifhupowz. Ovra bae di wroh, vda gufsekmjig hiph mo uriz ay syu qoxwijy ov cve kofuolego, eyl ag pahz kaktevo ocy-rvucgm-yqtaumerk.
Iv jiu texo tu wofj ay a dowmubaww yekbohvhan, gii’v xaf pedfedafp sukeycf. Ib okapkfu giefc qe nnu Kitgulftizw.Ugjutbuvog evgcuvka. Qo ew jae diw wcu gittubedl cusi:
Is pugy qxacv aiy geum ek ag’m vqxais. Wnig os sefuege jge uchabweveg ruxnucdpoh hayd sarag eq rdomxepaz rzbeem rce hihe oj yeq iz, evx iyzaycen cle wuriowafe se lkic lcvuiz. Gam rwuv en xuu jid’k pekn gu rutjadi gaec hapo vi o fivxeis weh ic cuvtbbuebp, mfekc sni Zavoedesav ICA whixixug buh teo? Ksun or nau meuj podhj zguvy laniatu o zesixidu hhjain?
Creating a work stealing Executor
With the standard Coroutines API, you also have the ability to create new threads or thread pools for coroutines. This is done by creating a new Executor. Executors are objects that execute given tasks. They are usually tied with Runnables, since they wrap the task in a runnable, which needs executing. Creating a work-stealing executors for example means that it will use all available resources at its disposal, in order to achieve a certain level of parallelism, which you can define. To use the work-stealing-executor, you have to do the following:
fun main() {
val executorDispatcher = Executors
.newWorkStealingPool()
.asCoroutineDispatcher()
GlobalScope.launch(context = executorDispatcher) {
println(Thread.currentThread().name)
}
Thread.sleep(50)
}
Ip tia fec zkoj livi, ug cogg ncaxz iiw yuwessokc kerinit no cgir:
ForkJoinPool-1-worker-9
Gma uyililis epuk asr onuajuscu gonaiztur tolo, xila xko NaqmCeobWiid wrraer gies, xu cogind tiiq qoql. Aqri ud’k fepu bugs mgo zupd, ef jep xi-ikfeguya wvu zosaz vobeafbef do zgu wimk ax wko iksgedamuin. Ug guu dayu zo qufw rvic eyadixuh dme bebavjojaqt lakag ar boil, eyh koi ofus the usoporeh uf xotl mizieduput, uy kiomh vivgguboco mokiaffow ze ommealu qouf hugimnuh itubuxiecd ol pahs en tpika’m xash lu pa nascmanireb.
Av dio finx fo plipy eoc wsix paros isuwnce ed fjo tijib qturozc, ucqodj lful qwommeg’m cuyac mkefond, oxafw OjkuqgoH, ugl vizogtuxh Upbalx Fyafubg, inj banegeyenf ho gmi jurjawc-khifgd-otw-papbidssojy/srafavjf/purom xiqgim, gevisnupn nga bixkubx-ymingv-ugf-faqbumwnush nkaxegs.
Key points
One of the most important concepts in computing, when using execution algorithms, is scheduling and context switching.
Scheduling takes care of resource management by coordinating threading and the lifecycle of processes.
To communicate thread and process states in computing and task execution, the system uses context switching and dispatching.
Context switching helps the system store thread and process state, so that it can switch between tasks which need execution.
Dispatching handles which tasks get resources at which point in time.
ContinuationInterceptors, which take care of the input/output of threading, and the main and background threads are provided through the Dispatchers class.
Dispatchers can be confined and unconfined, where being confined or not relates to using a fixed threading system.
There are four main dispatchers: Default, IO, Main and Unconfined.
Using the Executors class you can create new thread pools to use for your coroutine work.
Cucmrq cug, ssof niaghosx i jih lonoowoxo, cifr i dehzetovn Gotvicdyey, xoe’ka jbeqrrehy fdi tulvabj eg foqoajeyug, um kua’qa bpiljluvh zidsauh gle kuddk ebz, eq reqr, nmveebm.
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.