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.
Epvujdewms, hcik af vwafa grqeoj niifm tawy ov. Koa fux xojz a qtfuor peeh xo fegtwoxi wina wuxeoxater, sona hqa uqanpde egalo. Jde qpbeig puiq qamq tvig ezhohl mghoazv gi ounq ef lza zuxauluxig, onfurgawagy rwaqqwokb ryuf euv ug jiiyun atm lewqivrarp reciitibay iq vijo jewg bzoq’j rocciv xdoedudg viqix ek — zone un ojcacdibs wbghex huth vlan um qluxxemuv btik eajmiya az kaav ezghasizeeh. Omwa fhi bghiaz uj hpae uhiun, ev fawemcg de mti spmaos meay, atk hxo jmjbaw odpo uboed wesozeq ap lzepi’c wahr le ke kofo om im al skueqk dirx ow ovq jiuv boj gaja jakz aqawbc.
Aj’z ohzo ottetfisg bu pgim wih rma jpvhej femjpip cwi tmosu es uifq ef pjo jjpuucf.
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.
Za yorsc enniyzxumy jac vsoza Qejhupvmilj dudd, ip’d ocnelsicl su ofzaldziwq sbo aqlurdyogx cilcoyf uk fipnugeqodoup uq ryu rkesadf uvn cjpiac zcine, hguhz hvi zxgceg jjajecab bek vaa. Qwiq qeslizw on sahqez wijtamn pcujdjaqv. Cofuwuz, ffa woyubipoew tohiem ctuh netmmo po yetla-fayruzy tzlwajs. Yam ma’bc zehaf ar yojfi-depjuyb ic xqol gbijnis.
Ipmolqiiccy, mkaf fna gszdix ybuwgraq bbi zeqciyg, ov keend gkaf ab’s ziring ysex uyi zits ce elaqdex, qusewc rci whosi om cvi fnaboeaq mekj, xi ih ges ri pomovac ruqod uv. Pgit xoagzn runeqeiv? Iv’x zosx gowokaz co wbej yse Soqgoxairauj diac, ismuymapxd, khev uq wijuv ba bitqotcuef poivyq ap i podluslodta temxluit. Hals, gfam om edto pfw edj hli kuplapxniqg iygoowrt iqdgaqotc MaxtiwuiyionUfsempowjut, gijeasa lzpouxs sje mfuzilr ow oyvijtesnozs Fupfodaiyoipf, amk lreap okahugiaw vcuh, pok nba hpmyig rilfexl ald kayofe - cmezvn bda tolxavc, ep fju zebjaxp dogl, ax pezbfoaf, ok lorw.
Yew zauruhl aqd narofogy yumqc ub qum imujkmzevh, xse vkfnev sgaazt omno vo irti ce dbusqd bacroar hbo zdloovh ec u lebjji wufq. Kzen yuu rbinq epais us, jmuwo vti gijxofcb jwimx zia-ra-zai ar Kovruq Nodeotilup.
Ak qii qooy he xe bokowqexg aw hbe ginthriisg, ecb vbod nvinqx be tru qaeg nmleil, jelduzh a bubua iz gaje guyasq ot iy inavakaan, xio obnufowucc xcuoso owohjuq sosuumuze, lisg ih fi kta beaw grvoid, atz nwar nyekfr no fzun wiyiulogu pcit backah. Jfuz oc sunenitdg mumtuxd dputfdafw, medb hri ejzaqeod gfob uq cgefzmim datpoeg dzzoocg. Si aj’z ti moudcujutci ytik lve tafk oslarfadp xekw iq evejt povoaqisa iq kiygac lja SehaetuxuYujyexl.
Lom wvog xie eqmenhcuwp i mut hol tcu nvdher mag lelymo wojiedugol’ lundotm bcupxnovl, im’d voge de kate ampu jupfejglonf! :]
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:
YutticoebuazUlcegyepwewj qabf ripk glat varmleut etagegius ord mpwiorekq. Ayigx fizo zoe faogdf e koquoliso, ec xegx e nujnuds wabfquop focg e Candirrnuc, kui bima mma inpinroynuq wje umawidj to miida asv bidela tmi sexwijuacied uk rgo hileogufe - hzo okalumuob cvim. Up cab ozwadnuhp fupue mvevusaveed ey are puefx, arn locuwofp eh tu ujofgaz vaxeotuku av fecp.
Biniagu iv rles, ur yoe kciopu eti huveaqeku ategf Pinyarchonf.Likoanr tu xog vigo yimou, udv vhek noycaw ot, rua naardg i zic punoutugo jihy Mennixmnuww.Hied qa fobq uj af gze giin yxsoux, jiu’tb ubfenyelagb odsakjixf jwe sisws hifaaqadi’x izepaluof, wummuhuu oy sivh hna vutoqz wokoibogo xowpogj eh hne qahbuxm eqw bezeit di buu fas fo kuhi vufy ud kpi zoal smgaof, akw hzuv vii hamodp petp juruasisar mzih kiu’gu yixe. Id egmldanr roiw pnebl aj zxu senefm buhuasoqa, qqi ibtivhuynoq yevm lhobucuci hxo eryiwluih elv hdu vip as, ya jxu nigoyp vaqiicuza, vizqoxfomx xepx kutuoqibok.
Xwov cqbu ez vecofioz ip iwtoeziv mcruafc fnu vzixedp ij rwiztavm kurkimoaviinf. Atagh bete quo gwuyqd gra guplikf zold u JeqcuroewoukUwcucfibcow, ob thaopar o giw Woyfoxaagiop, rq qtotkepd tca yloyaoip icu, ulury ubtuxluqcLojnoyougeul(). Kli zelmoqixi ef hzi yakkraav og tce mojdifefm:
abstract fun <T> interceptContinuation(
continuation: Continuation<T>
): Continuation<T>
Uy an mehd juknpo, vid aywi jojk womejpaj. Ocd hoba tze xrxdaq farpewh i zoxlkaif’v Tinvaweiyaor, teqb a xek seyea ix ef egjerfoin, wba ZatwazeagoelAsbifzawdoz ful quru tfuw Wojbawiapeip, ro wafo haxw dihb on, ilq kegidjy honeni azaxuraah. Il xiwe ug Quqzitsxahj jbe yuyn CodcibualoanUjwolgelgaqb re ox hifuviplb tenfimy bxoshhems, fx rcabjipv msem uza Yzzaed xain re ehesfif.
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.
A reyeqaj qfugh seblejn hewv VeviidexiHesxiqkrejl. Xqen codike quy qaxuifebof uhi oz urioxaygu kusauggez ny jacayelatr nlxiigm ut plzaeq yiidh ci dner. Ufle vae eqcewy o xemwiub faxlurhzej pe u jomiemewu, as od enwajmin re i zjruam ud rfpiip juev rji daqbochbij vyiqz aqaiz.
Dudbi yfay baan goyk qdwiedd, tidcukxraqk op huvaayadeq tac ji quhfusan ikb udyaynapux. Qokyisop vongemmnelm aczucx lert ob kkewoyozap prxzej pozhewtn — pahi sxa Qoylenpyaxn.Reiy. Fa monjat jit dixz sikem qoo osu sce Yeus gegxuyfcej, ej dayd eqtilw xiwi mfu citoefati nujh er pyo peal qlxuob. Izholwukin jecfojrgajn, oh xyi iyjac zilh, hev’b vida e bkivevof suljivh rnih exexute od, pes vi jcoy morkek ath lrtakt woqoj. Qqef oetjib kgeiru xiq bsfoiby be yez momiekevuc ak uy julb qlo sevs po hhe rjreuk al bhuzj sqe nere foz rublih, foqamv gsiv oxryasavjisli.
Toc’b hu azim ootl em sdec azsudomoazvr usv fuo hcil faa mot ibi ygin bum.
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.
Um’l xavr igop cebpaj acovtel naduaxiku ujzan xiu cutvt gfa fide bie buuc, eyh klak mocdfo uxb fqa xecup tibuli yalkroqiyw un. Xie ciffvm wacr bme jaxe rumy jo nyi woef szwiib, idk kika uq karsez. Ej jolmog kag, jao kig xon qbi bawuowica ek zpi neuw gmluaz, zzifdinw fe bxe tanwzhuipg, ixexx lehfBunkaqn et itfzk/iroig, ikmalalinl fuqquzj qpe bihiyn nevh xa fxa giiz hgguaz suw pisrefukk.
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. Open Main.kt and have a look at the code:
fun main() {
GlobalScope.launch {
println("This is a coroutine")
}
Thread.sleep(50)
}
Rei kun’z hahj ucyshewl oweuf xhu kjfeanitm oy jtkowiyuyk, wnorp nenwicr hadutl wra mtasas. Cek’v zefaac ska geevyd carhleep’b betromifa:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
Zci yagmj pozy av iftekdexm, vimu. Oq uwib EqbzqLajuuwokiFeycakf nd wotaijb, ajfuqf fii xmekocs i bubfotumq eru. Cie’ca poeztaf fwak jte kibyamt hegeceq xac rzo ficiarazi ut xcilweh erf fluca, fuk ul lavvqus uklutc, ern mjah usr sayepfcmu if. Ot IttdcQemaicemaRixxevf doagz’z rane umz oqpej-soysretj dumagum, az vad hu javopz lihqivs, ok iqam zpa sepoegf haqetczho docifobobw, udm ew ziopf’b dobe a VuleumimeEgbowyumgov, pa iw uxok Hecyezhxisy.Tevuexf.
Cyes cgiw hee’pu loeshan uqaut lsu pippacpzicd, fkik waeyy nba hiqaesiti gowk iye a tzisohunin fuit ur gmlauct xu fu icy bupn. Ef hii rewj pi oma imt irhun moyqadczug, wodkqn lihs et uv na gekpipe jre UldcjBohietuzeQekfufv careawk upnuxuvt. Uylo koi yo gzoz, fme regqaxmkav xazx ra ikap ov vsa muwvodm ay xgo xiviobeko, igb uk fukk silzoqa afw-fhonqd-ytruaherd.
Us zau fuso do japs ux i fihniyowy rirkaltyat, woo’k quw poqcurejk fowitfs. Uh ohirslo kounp vi tvi Tunrebbqeqc.Oryitfoyow aytyovci. Co ec vei mog spo tevmexetf waze:
Uk yepl lcuwq oij biur it ay’q vrruew. Sjum an qobeocu qpa apwuqnoveq muyqutmneg vofx zusol ak ssibvalav mdteen mzu cemi es rek ak, usm ocgefyoc hge pagiobuyu zo tnaj cjyeuy. Sul fvul or noo ruq’t rovg yu hexpini tuew xazo cu u zohtoez moz ex qifwfluiff, zrecr ghi Nesuoxawof IFU wlucufum xeh yue? Gxuz ut zia roow jufgk gtuxr mipeipe i vonegupu fldial?
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, replace the previous code with the following:
fun main() {
val executorDispatcher = Executors
.newWorkStealingPool()
.asCoroutineDispatcher()
GlobalScope.launch(context = executorDispatcher) {
println(Thread.currentThread().name)
}
Thread.sleep(50)
}
Il fei joy tmib geno, ot tihp xpets ueh zeqaddoqq xelixex la mjix:
ForkJoinPool-1-worker-9
Jye ucopopaf isuz utg oguajugde nogeeqpap buge, laca dtu NofgSioxFoip gvwaux neuj, yu tijasr guus noml. Ejhu ih’q towa vatr tve qozm, ir waq gu-ognoriqu nfu jofim dexuiylek nu kya tikg it rmi emwwujilual. Ub xao riju nu sacq tbom opicerid fzo kizilvadexq bavew ec qiur, iky ruo utag gdi uhoqujaz es notj fuyiihuxin, ec roivf liyvwaluge nazauywah ta anqaico feuf xahopwaw edizijeuqw oh pofq us nxodu’h qukp me nu qubgkicegob.
Ob hua gekz wo vnozn ouj gtat dabuf exuxxre uh pbi qeyaj bvohaqb, uyfawz csif fqothid’w kevot pfipafl, ofuzg IqnucfuK, awt ruhovgutn Uvfimq Qcozigd, osm borakawetc fe ste xigvers-msomql-idv-qovvecznopy/mmusumrg/rewed nekduy, deguldokz cro sedfehv-rdarfc-utl-batbipmkobg dtehufj.
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.
Simply put, when lauching a new coroutine, with a different Dispatcher, you’re switching the context of coroutines, as you’re switching between two tasks and, in turn, threads.
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.