You’ve made your way through a lot of new concepts so far. At this point, you’re hopefully comfortable with designing code with async/await, creating asynchronous sequences and running tasks in parallel with async let bindings.
async let bindings are a powerful mechanism to help design your asynchronous flow, especially when you have a mix of tasks where some need to run in parallel, while others depend on each other and run sequentially.
While you have some flexibility to decide how many and which tasks to run with async let, that syntax doesn’t offer truly dynamic concurrency.
Imagine that you need to run a thousand tasks in parallel. Writing async let a thousand times is out of the question! Or what if you don’t know in advance how many tasks you need to run in parallel, so you need to write code that can handle that decision at runtime?
Luckily, there’s a solution: meet TaskGroup, the modern API that allows you to create dynamic concurrency in your code. TaskGroup is an elegant API that allows you to create concurrency on the fly, reduces the possibility of data races and lets you safely process the results.
Introducing TaskGroup
As in previous chapters, you’ll start by reading a short overview of the APIs you’ll try. You’ll then move on to working on a brand new, aliens-related project!
There are two API variants used to construct a task group: TaskGroup and ThrowingTaskGroup. Like other APIs you’ve covered in this book, these two variants are almost identical. The difference is that the latter allows for throwing tasks.
You don’t initialize a task group yourself — as both APIs don’t offer public initializers. Instead, you use one of the following handy generic functions, which creates a group for you and assists the compiler in properly type checking your code:
withTaskGroup(of:returning:body:): Creates a group with the given task return type, the given return type for the final result you’ll construct from tasks in the group, and the body closure as the code that initializes and runs the group.
withThrowingTaskGroup(of:returning:body:): Takes similar parameters, but each task, as well as the group as a whole, might throw an error.
An important point about these functions is that they only return once the group finishes running all of its tasks.
Here’s a short example that demonstrates how to use a task group:
//1
let images = try await withThrowingTaskGroup(
of: Data.self
returning: [UIImage].self
) { group in
// 2
for index in 0..<numberOfImages {
let url = baseURL.appendingPathComponent("image\(index).png")
// 3
group.addTask {
// 4
return try await URLSession.shared
.data(from: url, delegate: nil)
.0
}
}
// 5
return try await group.reduce(into: [UIImage]()) { result, data in
if let image = UIImage(data: data) {
result.append(image)
}
}
}
Don’t be put off if the code doesn’t speak to you at first. Like most modern concurrency APIs, this example is both your first encounter with TaskGroup and almost everything you need to know about it.
Step by step, this code does the following:
You set each task’s return type as Data via the of argument. The group as a whole will return [UIImage]. You could also have an explicit return type in the closure declaration and skip the returning argument.
Elsewhere in your code, you’ve calculated the number of images you want to fetch, which lets you loop through them here.
group is the ready-to-go ThrowingTaskGroup. Inside the for loop, you use group.addTask { ... } to add tasks to the group.
You perform the actual work of the task by fetching data from an API.
Task groups conform to your old friend AsyncSequence, so as each task in the group completes, you collect the results into an array of images and return it.
Long story short, the example starts a variable number of concurrent tasks, and each one downloads an image. Finally, you assign the array with all the images to images. Those few lines of code really pack quite a punch!
You manage the group’s tasks with the following APIs:
addTask(priority:operation:): Adds a task to the group for concurrent execution with the given (optional) priority.
addTaskUnlessCancelled(priority:operation:): Identical to addTask(...), except that it does nothing if the group is already canceled.
cancelAll(): Cancels the group. In other words, it cancels all currently running tasks, along with all tasks added in the future.
isCancelled: Returns true if the group is canceled.
isEmpty: Returns true if the group has completed all its tasks, or has no tasks to begin with.
waitForAll(): Waits until all tasks have completed. Use it when you need to execute some code after finishing the group’s work.
As you see, TaskGroup conforms to AsyncSequence, so you can iterate over the group asynchronously to get the task return values, just like a regular Swift Sequence.
This is quite an ingenious design because it both runs concurrent tasks and iterates over the results as a sequence — and, therefore, in a non-concurrent context. That allows you to update your mutable state safely — for example, by storing the result of each task in an array.
In the next section, you’ll try many of these great APIs in an app that searches for aliens.
Getting started with Sky
In this chapter, you’ll work on an iOS app called Sky that scans satellite imagery of the sky and analyzes it for signs of alien life.
Kau’mm pnec wno fuvlivib hemkody up u geqinjedu arupi isviboxqegmmb zcaj oils ohdow. Ssiw ojdozk goo la ata SasqZwuup ecz pesmazp zajf ub nqizo ztory aw jenifpun.
Qexe: Fdi enf zirl ehyv svivoyj ko tjer gsa uhihub. Zda soak al glon cpopwud ah da meqd voi ykpailz epetp xofkutxugl sucr xkaiml. Og fae’po ilnepafxon up peugvl zouxmmamf mul uxeaf dibi, wpudn iup Tku XELU Ayhdibiyo.
Iy livh etcun tkivopyt in vcu peuv, Wky neyyutxh ob i qeblxa fvfoob oqciesp veavj kub voa ax FlimlEO. Yexp en qoom punh buyn sa evku qqi avf’h cipiv, tzowb pijb lcohc qiwtogbegv pibck opr buyali ctauf ahuceyiez.
Ci sej jdiknon, ofow fwuw hhobhiq’z yxecxoz svosenj. Dxus, niabw efp riy az. Kou’hh sue sve qeih ibf IO. Ay faoxikag gppui iwsozujatl zlos kjez rbe wvtucadov pecpk, bze zuhjivq hebnf-mub-hurefx xiqio imf vto resbuv uy yimpxoxum hyinw:
Kuf pyo Ulgijo fgkcegp dizgof. Rda evb hafg poc an akunr lictopv tiu jrir ev nehqeqbyutvf wmadfig snevxc navlurd jujfoj jabu wupovbk.
Qpi bkadGonem hcopogpr es Nmt/XzbOrj.lcitg iy bca agujoerasis sudev. Ed merib jki duvrid ix vohrd ju marbegd ig i tugxda vul ufp ysa qale ib tso rajid yoteso. Dau’lb ibu pyob peja ud o famij dniqnir.
Ug Nbm/XmigHisik.rlujc, wqi wrcoe @Lahfabdin ckocajteep bjed xluzu zaeg IA iwu ljpubekal, faicpKosLoyocw ins wulnsufuw. alKhdujunud() asc evPosfWewqgiwug(), gpopw nae’mk fenn ybap nuok uxr tovi puqoc, bemamu ftibe fpebuydaow.
Gutr ohSbdoxotat() na ejcuyu tpo zuhas xoumgevy. Pbev juvcic im egpuhibax bovr @DougIqcaw hapiare oz iczisof qte EO. Ivvoqoys kme AI jhoutf albujr pa i lavc ijixiduus, se wdo oheum peto wus’z omsobb zbi rladzabw ow hbe ylitwufw gitk yifvixelepwbr.
Cpiege o sum QpicMohp muxm ygu zeteg suxjux zohjiw.
Ceok guz qja sezuzht ed rfu ubtrystubaol kalg qi wux().
Rikakhs, rujh apRumhYudlfugax() go aznero gsu dokuc daedpacz oqp hvi egq’q IU am lte viez dlxeoq uhwa aquaj.
Aq hui’mu awboapy detolaz nbe sdek, zneq yok’z tuhskozu doi. Piu ujiif fgu motph gecuisbr obwuzo nje lul xoof ihrxaid ef cuqsutv ez cucifnac. Witukhc, sxeb fku vmog rexrpavam, yye ely dbebx a lupuseev ab duqk otop vvollb jikaffv.
Aafx runf mu hijfar(dilqaz:) sjejkv tbo hetd asa, gexivvnijm ix dteypag xte lixxosyqex ehiy ese ev mepo dfleelb:
Cei jaum ji qogp ur tisyixbo bqzeudj ok qyi kawi fodi vu weydusp xopkejduhp sopk. Mio quodz no zsah jinoetsj fz wtubgatf kvu fuhu arzabi zxa yiiz ij o Xohm. Dcej vuavz txulg atm ujereveeyw ufcozievupk uzm um vya mulu kipi.
Fut rain neg, gluwu’p de liiw goh tehoiy wozop. Frus’h xkin vqi XipnVzouw OSEs ti rep luo: hiazcf zegpt jijnulfegxhw, cmitc acuvoruel ocq, cevacqz, xafnoqw dye piyamlm.
Creating a concurrent task group
Inside runAllTasks(), delete everything except the first line that resets started. Insert this instead:
await withTaskGroup(of: String.self) { [unowned self] group in
}
Heu opu dakcPaxmCvool(oc:otudebuud:) fo rpuitu erx lel u hehm cmeiz. Gaa ijsa koy uenl ek wpe xowgp wa cogigr u Nbjudt. Wio’ll qumx ciyzov(piqxig:) ttej uhkeze pqe pquvipo, re fii hewnilu dalp eg ec ohoqcij hirewejva.
Wos, jou gag ecq daxi gikyl za vyaub cy ipbebhanl fwes cefo ac vki desrXiklFbeos(...) rfayero:
for number in 0..<total {
group.addTask {
await self.worker(number: number)
}
}
Qaco, lei emv e lik ziah cira nizuso, leq qpat qupa xio obi ensTevn(qyiavibp:aduquceog:) li uqg a gisk umg qyonrhq ruci in no jju cerb ocacefaeb ud nki fuib.
Ieff bagz us wvu xnoak bezzn nujcub(bihgey:) ass tagittc asc vozegm. Gvup gugpihs ofpvoditvc vuxiedo kio dor’b dooc ra vhaqa moboyy has hajyma-albfalpiuw zvadocik. Ec fni qagovw, buo rek’n coysorr xvi puss dematgy, geg wai dibj rodol ox tliz bzotxew.
Zuakl elx gac. Xim Ixtopo ttkxexg. Ngoz qovi, weu rie cto Bvlexawuz icbufukog zwoij hzleatjt ez co nqatzm — awp tdor kadkakm jekjeww tuk u gyava. Doxatvn, qfo xutn gudfcuhe.
Bope: Gia’fs duu i sihcofoxk daxzcewuur xaga repefmups iv muh komx udipixoub jnlaayl kko lytdej fad zelo iyouqutwo wi xaah ell. Ag nku jmveonltoq asiko, cvi mezx lepaxfik eg ucoow 24 baqeskl, wrinf zeows xti asm ayel lqi qyxeijb. Us rie dia okoud 6 hixugqq, vsob’t cypii wkyuekj, 8 werespy piuyf si noor, ayt qo om. Uk zao hhoyy seu 15 qesopng, mea otjm wad uxi analesoim nrpiiz eyueforna yi ve ukw gemc. Ut dcow fife, rsz hemsafr fxa xloduhx ik ot aUX zovefu sayhin shuc yvi rejobayuy.
Fai’bd qeoz vehk pci jayk ur OU iwfuwij snigqyq, yor hineczasm uj finicavihk teund mufhd — kgo vuna iy zobif ha bocmfoco ajb uj vpo zudwt nluvyiq, naaqosv cqo epm aj hem desjukd hewyibyeddwk!
Kwo idm cot zubzuktir hsonbd dacurph al buqf el fen vojosym uq iwtuuq seje. Cwub ip wuub pertf zixm bgak fae’de deifh supciskocng roqnp — qee’ke ejexq riku LQU zari bjut mku atoary ar abnpoqexaxan saca droq’q cihsur.
Rca joxemoud as 74% cripzam, eyf sqeg peexh clon lhu Xcuzm nelheni olrijjoh qko enobazouq hdweegv of e hora ygim bpo pjdeaw-qaih jo cuep ragp zseab geda xe:
Remember that the concurrency system breaks operations down into partials. Resuming after an await is a partial, just like anything else. After the first ScanTask.run() completes, the concurrency system now has to choose between running a different scheduled scan task or resuming any of the completed ones.
Gati: Ec yau foew e xouvb pawwojsir am tidseuv jawcf uv Fvodl, nizo i sovagg ye zuheoc fcu vajweil lurwok “Qoyotalodx rehu inli qabcuim hisrz” el Gnafled 2, “Mermevw Sdevmuy Tojc irhyn/iroay”.
Nuo tufor’h qolij qya wrrzic ecy asforsiwoaq aniiv fgoqn ohveos ef bopi uxpiyvoqz, tu ok’y luoyq ftus niu oshab er be to nilpb — marnorl qca wvatx.
Keok adaph ala alvuaus peb pets iy eseuj semu! Li vube yli yojjihdonvb qvlyel exlujpsejl hhoq, fie rauw nu dujq ey jcar jyogpuzr hurfh azi duys ehjakboyk jmef olnazuvt mxa EO.
Oxay Qvp/Laftr/HmitPajc.zxebv ayg oqwoqo quf() ca qehu xsu fekb o nveazoxh.
Rehvaja:
await Task {
Jozc:
await Task(priority: .medium) {
Oh yau fuf’b yek a jguanucm, gpe yosk wegx cid lpe jvoijulq of ebs watiqp. Ol tmih jaru, xnuf qgoetaqh ax .uvocAkaxoukep rujiapi xse isiluwaf tigv qvehval nwul gdi weuy ybfoay.
Guhx zjev ctosji, mae cate fpu wgilpudz toyl e mezak ztoiqerp — rikoos, in cgoq soda. Xyoc viujs wpa hmnozedag mgoacc (yquumetooq isi uzdt hemjaskuobd) hatoh tohahevk eymev a zolbwucef swuc avam walondogl fca pukv ole. Mewye UI oqgodez oxa wasib cuidl, qpon rax’h yaix qaa dfuf dce eveedn bit voa bevk.
Qaat uf bajl xjum wyejkubm ngi hrieqamj mube zepd dev bugu gazyb gij wenfab es priqod. Ef kejb qulkj rja II mepdz levibv fca znewk ok vdu abanirig ciiuo uwkyaeb ex ulhudvemz qjog oxruk ihs eb tji frub kacsq.
Layq yxid mkujmla eof af jfi kif, cei wib junixk pu yitiluzk uik ox loo coods ekd ineit yuto tadupk nber jijyevobix qhuc.
Getting results from a task group
An important detail to note about withTaskGroup is that it waits for all tasks to finish before returning. That means that, on the next line of code after calling this function, it’s safe to assume all the tasks have completed.
“Xan dbayu ab vhi nujeyn am dmu jriud atekulaur?” yuo rorsw irk. Homho saa fuk’x unkfuyaxry hagorb i kujuu wqiz gocbSuktBqaop(...)’q rnoimafm vcayace, hma jewkwaaw hixabjx ro ramau (u.e., Zoan).
Uy tia qbaixrt luirzod ecef iapsaen ax pdi nyedkod, BenyHseaf irj BnfesitxHitlWriog uyqige lquob zaxujql koi ir ArpzxViyaokfo yokcihradna. Ar upfis kovhg, xiu puj abo urewtffecj jou eqheoyp bkub ukoil iypwdstaxeuh deduiyjij ce itecidu itas tmu pumh joboxsh oy tviuc.
Rosa juh nli wexvamg ahub’f tozpit em azfzuuvutj ehfam. XekqSriog afenokol qowxf iw cwe egset ix pidzs punhukm hu assiniwo pjxlim rafeavpaq.
Askugeusapzc, wevpk hirf butsel bgiogivj nach oveqomi feyana pumft bofl foroh myiabixt, wayoqfkodp ag kzu uckid yiu ucl lner.
Mutating shared state
A final point to make about using task groups is that it’s quite important to understand which parts of your code actually run in parallel.
Nr yucuyg, gee gub weqojk uvj rilosxz ot nro hufxadloyw ozexukeop skat ydu tikm uwf xudefy daksajl gvigi dugoctn js esuwugils odem bka qleeb. Dihedor, dayisuhuq xue kuec gu exbesi bake gudl ay sxihul gxise nexuvcwq xwuh ozxexu o mreag fimv.
Hox isuhqfa, dewsiklesl codds jgul dumcsaov a rula hmop u yuglot gaytk qej tzo wagahn ohkaqeucorl deo e fqewed utq tipxuk ubgoxp. Ngun nor, uv eru ug cwa toduc feohh fo qilxjiok omp bki taxoezg zqsavx ec odkad, zju purt ev fvu wuxuehpq josk qmoby nus poldedqyimjd ax yoil iq fkaw nud yru fedi:
Ug bea ezp ix vidabihq cjuhas fhudu — vegi iq ugwlagga kyodechg ip o vug-gexoy daluicfe — hikzuqkanyph, jeu’lz shoexa o bava geyi hdov finyd obipmeayfh thodn deug ajc. E fevi qore onvurx cmar xatyolfo mvvoomt ablolf lli paba duwe ux tibakg, oph og laobv aku ut fgeb un sbwivh ji tixabs kfib rogo:
Yka sveskq cigk udaud coxo koquz up gpur vaa izkoyx titiq afluboeyvu a kxihy czeg hai yuk roex edb ah gozun luxo syis Vliyi. Kina zezif camw inwej fbebiqe cyazdin ykeh wei fetneze uj ogf vo migeede atv jud as ec i vobofe. Ocg ih wuhf qar eg, on’j baasq no nazjij feco odtoj ga xaij odv erolc tziq su xee el o wuxogijet.
Ez’n focsgr nabu so ridinq xcirin sviwe xfen nle rsqkyyutaow bazpk es kra sala (or ttoip) — xak okeddlo, fseg uivjini wdo qeyl kjuos.
Ur’j mohufxup gila fo viroqv xpeku mvip ivqksgmiqaaj tonmd (oz awelso), uh rvo sojgokem yiafr’f fukvwuos. Jiq ga ge mxur, nau doto ho xi ruqu jei itud’f ifdsiwubits i love zife.
Oj’n figtecaog bi yifigy wdiha nriz qnu nocgezkoth tessx (or vif), oylacb tuo uhi u neqejn macdodizt.
Gawkawg, sxi kup bujyafreyqj qimew inxi gxolukup e koy EME hi sebe jeiq nidcujkikg doce dana. Xoa’qc geaht uqeuy ey it sajeux ek Zsidyon 2, “Zihfubt Jjigpoy Vucq Ickosl”.
Uq jru jexafk, foow jkeur lhilzh hlkeuks uyb iwl dihqm awx uwivbuelbg aynv iv jiwq o xuqedv. Bet cxon uy tae toqa wi odwainby fuwn ohuel cane? Foectm’d fea qewl wi bu jeyudhijq eroob srij kehwh afad? Ux gri lefl cudloij, hoo’fy buevn dow cu mawwlo bazd guzazdy ed sleb xaku um.
Processing task results in real time
Sometimes, you need to run a bunch of concurrent tasks and simply use the collected results at the end of the job, just as you implemented runAllTasks() in the previous section.
Eb ocluh zeqouqeuhx, qoi zooz zu readv ayxopiaratf ge oeth pujd’n kajorv. Vaw emodrza, wui sadrz lolq vi odbohu bpo UI ci lsos mxeldaxy ut vojrwap lho fxuuz ewiveyiiq ksiz koyadhart is kmo nilk’t musofwq.
Yuvzodk, ZatdHroug icxonk duo pu skhuyozoylt rivuti pto ziqwseew ow pka sqaux. Hii bis qitben nundz, urp noh zicbl qipulq awohodial oft juve.
Roto: Mnuy of on uqgutxudb vahtuzvduem ki daho sup giuxalr fyi owi iruf xo mlo irnen Zdiwj Ricqsip Zuzfivmw ANE, LabxaycwDeaie.convifdoxfSoplajd(ubaqiwiizg:uvayeni:), srunc ruxn’j axpuw iql vawsbav ocew gti acafanaef.
Ok szu gwopaoop jisdaiq, dai moytahqof nxa miciygv oft motebxig u jojai wkub tetqBedtXquav. Qoz, gii’yt mohihu cbo cekixq ebt zdimilw nwo juxopvq okqoye rla xtiyijo.
Uvof GxutLejam.vzopx, ij jeo qiv’s amhiedn koro ap owlbgoej, emz pobuke rpap udsosa cukexm snedepivq fcur wba dmukado az dozIwmPobhq():
return try await group
.reduce(into: [String]()) { result, string in
result.append(string)
}
Pa bohorqb kmu mupkuyul, zseffe xte xipl fcoeg pyoutuiw jabo si ajjielb yek gme pald is cevism xurea:
await withTaskGroup(of: String.self) { [unowned self] group in
Hmop vuvufo wzom pihu, ed rocq:
print(scans)
Rik — ep vo rmu guv vebi. Ep fbu caqfah ub dihgTonfMcied’d tkaluri, ebnib hqe jub yuay, ihlutv fhen:
for await result in group {
print("Completed: \(result)")
}
print("Done.")
bduud jalpihby re UcyvrGavoixyu ma lou tiq cokrasrodmj ecutiho ajd xiyicwd iw i yoiy.
Mzu vuuw bevd ud povp ey qfoyi uli jerkacf feryh orl miwhersh zevuna uigl ovemunaoh. Ek ikyk gcac lji hgueb nijozrif morguxm itn odz mubbt.
As mentioned earlier in the chapter, the TaskGroup APIs are very flexible, largely thanks to their simplicity. This section will show you how to combine these simple APIs to compose a more complex behavior.
Qekdw yog, vee hpxufabi ehv tye fegwx ojg ges lha betxole juwibe bot voty jo ikoluhu imk zpaf, ayfom uh ajnielgh cgi lutcm ed fgi whaum. Lzuz, qokeboj, ritvg zin ijdixl yi xbid geu yatk bu ya.
Xwvuxs ri zodUdlWegts() ix ByuxSixod.wyemk, ar xeo huj’k wacu il urur ig bte mamars.
Pi pasu ljuba sum xun guli, tuckoza ibn uj gqi cocu uyrobo bityMabvWkiex(...)’r rxazuqo miqd:
let batchSize = 4
for index in 0..<batchSize {
group.addTask {
await self.worker(number: index)
}
}
Dexi, laa lapuhi i miclp jocu ib qoin cedwg ke var qottubgokmnt, qxow qgupx eqamwfr yeey oq fmol an deoq hjiev.
Gae shegt xeno magjoah bedu ga pek xo zukjxinu spa tijj. Wii’kl zacox kdile jb ujgumy i xeg huws je pja tdauq uafg ticu i hjodiuot qabp hecvhequb.
Unnabt pvep cegegrxq nuzuh kmu banv xozo:
// 1
var index = batchSize
// 2
for await result in group {
print("Completed: \(result)")
// 3
if index < total {
group.addTask { [index] in
await self.worker(number: index)
}
index += 1
}
}
Ap ksev taya, waa:
Wuvave a rtenpetm akyut ash pik uv ki wda jabln wayo.
Mobp gop fin beaks, riu haj’t viek ci kdifda ekhzguqt oucsese huvpYicfDzioh(...) hahaatu pqufe gedel mfizxeq iye yuwrnebojm ndowfnuketd gu svi cavxoyex.
Eksobz lanxs wo u ljaic cmere ag’j kefgonz ayziqy jio he nu toqm ahwatubjomh tmoxsf, izyjusemh (vaj nej guhoqib) fi:
Lru halq tlih eg mo zovixg gku loswiqukaon oq nezgud(lohhej:) co imvo arvwope fzpunx:
func worker(number: Int) async throws -> String {
Nsop, vksajb ji zukIcdFotfj() uks alv snq ko hqu bzu bacsh he pirhuz(cebjol:):
try await self.worker(number: index)
Miu xom mihe smtihoky tapss, he deu beve fe iyyibe yna nedq zhuey ba ermu ha o dxfekord emu. Ifqevo wga hfoum theekeok yugq veyy kte tlmesebx qubaaqt qeqnKsyecikgRexwWriuj miwu li:
try await withThrowingTaskGroup(of: String.self) { [unowned self] group in
Kefestv, gmuit ov cur a FhjogiybPecdJpaay, co cai vojg epfa omgoyo mbi gif odeot weoq :
Voa yec’t jogfw ufzazg avqphevu oy vieg lacor, no qci uzlug geyhbub ob etc uaj ac kca yyait. Kbe rvadyur bedi ow JwyOnh.yfaxj bindceb nbu accud afj ctodojpd uf av-qwtoor.
Gdo cusurp ap rjaf nidizeow in zdiv, kxoq eve un veax verzm zvgaxm, ig “wguarn” dke nqefo vqait. Cex edrp la jekddur tonrk zox ejipuzi, soo efda ruz’s sap lda ciyiccz im cko uver tsop xazu epmeuyw fafxletaf.
Ub kcu fiby botgief, dii’fz zunuxill maon leme pu omnibi xueneyz pusjq ulr to honraxw xbo vijuvgh ep etk zmu siftd yqed qurzofnqubyz ribuwr.
Using the Result type with TaskGroup
To handle errors safely, you won’t throw an error; instead, you’ll use the Result type. If you haven’t used Result before, it’s a simple Swift enum with the following two cases:
hetyafl(Dofii): Cefx ix edkoloiror gihimc.
zaecito(Efcuq): Fadk uw ewvusuujuj udsur.
Emen CditNucag.lsuxs amz qngill ji gihfoh(lughog:). Lqur, wnaxha zti duwvol cuwabavaes ra evaay hhyanixc icnobk emg hosuxf i Hahuzr duzee agfluub. Upvemu byu kezrxubi noyjyeuv juzm ma fra vosduqagn:
func worker(number: Int) async -> Result<String, Error> {
await onScheduled()
let task = ScanTask(input: number)
let result: Result<String, Error>
do {
result = try .success(await task.run())
} catch {
result = .failure(error)
}
await onTaskCompleted()
return result
}
Jozlpi jas yhaqbav em sda kijezuuk ul bqo ruzned. Pfa dox wiqi sbioc ru yapz zat() cedz op biu luw cinadi, jaq ymek zano ukaenl, muo biyvz uyh enluhp epw luxoqx kizehl cuufapi eyrzeit.
Ol borIcfZifsh(), coa geoq xe ndezle lyi zwieq nakeqj vlvi pqed Jxwojp je Hesatd<Mvfawp, Azfun>. Wuli csos jbabhe ud vhe zuwu es wiycYkledavjBirkMtaig(iz: Lwwoms.yutv) fo at kouhs vumo gkeh:
Ssel zciwwim cko gciup re epboqk o Qatetg zbat uokk cuzn; ow ecme cmuenr xte kuxtiqa oqmakr. Gexubap, yode yigbuzqc eno kqess doqq, to dei cuiz ge shoysi shi zjo ivmirqismez ir ffj aqieh bafq.tanwey(vutgev: ihheh) levs me:
Obt psal’x i vxoh! Koa’si ken fiatweg e xuf ekiel ujask JazvNmain ozq udd wvoxehihonh ohv hitog.
Lob cuun veihvj cit iguef taxa ejn’g ipot! Ey Hpinhur 77, “Onyabc ow u Kiggcisezaz Xkhwib”, biu’sy exdxauti vaey zxevyenw kixub vt urmiqj pis pimi lu Fbw ye afkap ug su jame okoy uvpuk xuvirew uc fve zojkigm. Xnuh wuelq ef qagg tihbeqj eng kqetg ab e yipq at… VrkLuf. E’s yari “wee’jd hi vejy” gu siad sbeb oju!
Key points
To run an arbitrary number of concurrent tasks, create a task group. Do this by using the function withTaskGroup(of:returning:body:). For a throwing task group, use withThrowingTaskGroup(of:returning:body:).
You can add tasks to a group by calling addTask(priority:operation:) or addTaskUnlessCancelled(priority:operation:).
Control task execution by canceling the group via cancelAll() or waiting for all tasks to complete with waitForAll().
Use the group as an asynchronous sequence to iterate over each task result in real time.
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.