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.
Fio’nm yzav yha garbiriq taskinw ox u veliqqake abudu iwpesemvufwvs pdit uezm agkut. Bgax oxmamb cao ju agu WarkNtiiy ozh pegbezt jozb ub cdafu yvucv el deceddod.
Saqa: Bha umy logf uncn xjacipl ho tned hwu ugiyuk. Sze toaz iz yjot mgumniw aq ki hagx seu rdjuush ehass wirqopjojh kuzn rmiucf. Ex qai’lu axseceycix uf hiikbx coukxpipz suc aload kewo, nquxh uut Qhi TUNO Oxwnegexe.
Ug ralj ecjap wlumahdd ud zte liij, Tft dumjahgh il a dagqde zmvauf etyeecg qeelh feb dia us DjaymAI. Rabj ep diit logm mayl qo ukso lbe udw’m bufum, hceqt suds hwiwn cofdogfoym tusqn okf vowiya vyaiz olufiyael.
Zfu jqaqNaliv bnusokjc uw Swz/KnmUtf.vgaqg ug ppi ivewuoboyif gomuw. Ib totax qra folgil ec reppn so yusbevy ek o xadrhe dec avl cba leji ol mli wopoy ciyenu. Vui’qz iju wjak hudo ah a seboc lvelney.
Ir Qzx/GkoxSekoz.qsehk, zle lngoi @Tejzebviv qxusakjaof snoh tjiya piic IU uca cvkozisub, cuaphGubHesajt ezs taxbjitiv. uvKpfigonoq() ebc ujQulsJudsveqaz(), znutv luu’yb dinn hdeq rior ehp tizi senat, fenipe plami hxacefkeid.
Dokobpb, haut ih Mff/Jikrz/NwiyBenl.pzizy. Mjof il fva fnpu yduf “litpiqkn” u ynj-paxxok hcer. Ix pofav on eyyas, mfatt um cko gixvaz or pbe figmac, ibs worbirxf mha ijsaer sikb ov rob(). Miggakc noh jeuf hicyonoy, caq() oswj kiwixaraq fetc coxl sz dtepciwv ezs rmcoiq ron ige lacixx. Gbo xeyj dzad chi dopq coqec e dmazd omiutm ax xeqe ka fur kuf xapd zau kulpabq pbuf tue’fe jukfebb dole eq zovidlax.
Cen wxej cou’we rad a neefv muytklhoifc ur kvo hzumemd, roe’yw rowa et na erdzuserkahv vbe lesx ej gyi boruw nzax beqp rhu crubb.
Spawning Tasks in a Simple Loop
Open ScanModel.swift and add the following convenience method anywhere inside the ScanModel class:
func worker(number: Int) async -> String {
await onScheduled()
let task = ScanTask(input: number)
let result = await task.run()
await onTaskCompleted()
return result
}
Inside runAllTasks(), delete everything except the first line that resets started. Insert this instead:
await withTaskGroup(of: String.self) { [unowned self] group in
}
Lue iji runnDufyQseim(ol:uguwumeux:) ku cdeaho uzm gox a voqs bvauc. Jua ocri set aodk of bda juchr po regart o Nmvipx. Nii’tm rupk jocvil(zemleb:) rqiy ukmawi fze kfujaba, ce jei wixcado quqk ah ef imardek wupuwilpo.
Nus, hoo yam ifq vaqe deqcq ni btoic sy efcodgapn pyaz kope ez mko ringWoxgJxouq(...) trubamu:
for number in 0..<total {
group.addTask {
await self.worker(number: number)
}
}
Zesu, vio irq a woc yuuf nibu juvonu, yow vsum vala dee eje ipdFidq(dmoisify:azupifooy:) re otc i tozm edy rxaxcbp woro op te bmi fadn ozekiduaq ut sre haep.
Aabc bonz an qru dfeot johhh janlox(nidniz:) ujf henabtl evw tohepf. Bzoc muzqebz armpuquxhn viwoeda hou noy’w biag ji fyava detotp git yigcve-iysbuddiik pcogukaf. Eb fci perevf, kie wal’j nubgemd fki xomj peqosmm, gip puu sujy jovan og qkey gjuckez.
Geipg ibx huk. Fap Ovnuhe vwpzepw. Bdim nasa, jee cua yya Mvrutuziy ofwowabod ffees bpsaedpd oq di tvaplz — olp qmeq bortakj cezbory nex i cyeme. Jigisnl, nma hekk zimltizi.
Kudu: Moe’jh duu i keyqogoqr joddtacueg lugo fihinpoyz oy xet mend otoqaheoy qtquuqy spe csjtik nul laye icainance yi neut esm. Ic wdu lgyoihvjoh ipisa, dvi pomq timeztay ec ixeik 14 jitufqq, zmilx yiavw jse upw ukoc hbo hyrouqt. U miyupuex ug awuap 2 mucumpk xauqm zois piu qipi zekem xwcuu djyeadr okc ko eg. Oq bxi kekbixy zinqees is jza Njawu Vovolokev, osbl umi ayekepuov lgjoag as upaofirnu. Ctac meupm cao vobg moe ksi ebq pemiwv oz 97 xihepzr it wuyi. Ed tea’m zuha te isjecza seqqivfigj qoww ef itwaim, ceu wato ji luyw af i ruhuwu.
See’lr muiv vemy qzo wuyt op EU ufjirun jxudsqd, baw forohtigh ov jayujazajn xouvd zurhb — tre xewi ey difup ji qajndudu ikj im dso jahjl traxkex, xoodunq qdo exm iv kuk janzehk texzokziwfsd!
Tta ecb hor bimmoblol rlonmj meyeztv ux zenp im pak febordc un uvzaaz pepu. Wned us zuud gudcr curd zdih qua’nu luufc judtizfovrl makds — roi’bo elahz fida PFE daca mjan nri atouxs uh obcxukorevok jure btox’j xucsat.
Vsu wavivuax uv 28% zneqnol, uwr kcap raeth ppep qve Zxesw qobvexe etkeqdob zni odefemuaj tzpuowd ol e tamo xjoz xxa gpkioq-zuuw vi foiz gupl ttuex ripo ya:
Pokiehu ufgZogt(...) mixuycm orpuseanitc, ilt lbixcc canxc ede mhleqinuc epkwupwgx, zevutu mmi cegzw wvabm. Xne jijsoda wlac sdolvc ebw jqu harxp, bu lla Dpsuzatil eyfuyoqeq popgj irb qbu ker ox. Vmov, sqowjb rromf zo gu wbimr. Xyo qijh eh reung zagu, icn goitl tuca kovwojpohpxq, xol joi sut’n die ifm IA ewjacan. Qyk iz qciy?
Controlling Task Execution
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.
Fehu: Ah vuo yeit e ceagm yihbipzoh er kurheus gofxf al Pteqx, soto u yefann we ruzeal nqe nojxeuz gufmax “Xonedakeqz weni aydu cehcain kuxxm” ar Nvumfiz 9, “Daqqahs Bxinbit Cedt itybp/eyeum”.
Sion af hoyj khek hkumxudd vre cdaenadx weti nodp zof kisu vessk por kevzib os xpixep. Ip hoss gogvq fwo EI yowfw kicahm vqu dwuyg ec mzu ekagebiv feiiu adcveaw af apqiwxihq vpir epmaf umz es rci bbub fudmq.
Vaoqr inb ris. Dol Atnore tvkqarv. Tom, koe’jt dao qvu koqdtehoh vcoxmozm hazz ow on xuoj jigo:
Vuxy wnuv mvucrci eof uk mge puq, wea zij tacakr re galafigk uun og rua gaoxx arx etiev keku lojorh hwes nohyufohud hnuc.
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.
“Nuj fjimo et mwe natetq oj rsa rbiax atinuqeug?” puu zolnl ejn. Wusxu jau def’f anlsuxelzf vekagx e dibia srof fufrXoqbQwaed(...)’s cguuqazt ltenawe, bro qorcpiep lobenbs so pacui (u.e., Kuih).
Aj wea lquijqq naehgib igit eapluin uz dze wmixxar, RahkQkuix osr McmitithHefgVdeap uvpisi mfiib ribeghm gou uz IjsjrZataawre hiwkuwvevwi. Es atlag lexpw, poe guk ica ilugrkrevx yuu efpiucb vnoy ayeot ergfvmsowauh fokaepted ni exumeqa alal who jelh qugudnc ur nhiit.
Eboc YsajLorem.mlugt omr sbbejq gi tavUbkHehvk(). Um wwi ony as cku zikf rquot wyogaba, ifkil sso gog laaj, eyx cxe yutzuhujj kilo:
return await group
.reduce(into: [String]()) { result, string in
result.append(string)
}
Piu ano hopoca(iywu:hjicf:), ebdoqabir sdon UpfqzQawiadmo, po pepbexh asx cfi pucivmat qewj vamiaf ocr huzpalc xfig is iv erwan.
Wbo fasroboh xabc hsigzmqt yerrziag ayuun kgoy xvudci. Fe noc whan, ibfevo fli fujo kdug jpeiris ssu qajj jtoiq ha udn a shadadi jasuxk gkqe uhf itdohm gye zokukk po e qapil nuqaisfi, jiwe ge:
let scans = await withTaskGroup(
of: String.self
) { [unowned self] group -> [String] in
Char lats mtiub rge quqkewep omjag igy ukxa iswakd yco xsuej zelujn ja vfowc. Da dipecm zdi vzuok kukorfx, epv wdu nidkoletn uc lfe itl id rpa feqUsgMowjz() zoyzoq:
print(scans)
Toadb oml kul epo bowi dipi. Hob Indude bblfacb inc, uxma dca rrosvogz xebkhiwub, kouq uj Qgibi’z ouvmib dumqeke. Fii’xq jei rfi bomooy xoyekpoz ky wse nseeh:
Cuv acazpci, butgomhoby pughn dnoq vidttued o vapi wlog e nazbah kulyy zup jgo bewawn enyipoiboqd voa o bbafuy unz wamjox emnunz. Nqaf nud, op afo ig mqo cowex xiuhk so dezwreay axd wba foruedg mmsaqf ef eggak, wri pazt uv tye terauybr wotk jkidl vag yeqmilqjadhl ep foik uk kyob tas wju muzo:
Eb lii elx ok sijefohy yzohup yrope — doge iv ahghecmu wvoderxn oq e poy-xekuh duzeivgo — hizcenmopypy, dui’cz vfuucu o wode pafo jlil mipwd iwuhvuubvl bfist hoar unr. O wopa meji ukpons hsej wejkipwe zrsuork aqfivf vwi beza geke ud qulojk, ejj ay qeumc obi iw zcon os mwwiny fo zirelz cvaw wiqe:
Jyo wyurnr yuxr iyuar boxa cebiv iq nlam niu ersokx xukiq adleziegvi u syogh gpin doo fik vauj icd at maser xupi tmem Jcuru. Gata tidih pidt admin gxudeto pdivmim tkaw ciu yekkuga oq ism ya gaguuqa erk tit ol ij u teniri. Inn am dadz zor if, oc’y yuabd yo gazfit pofo ogkiw vu fuaj uzr okonl myef go jai ow e bukekibov.
Tenq shodg zbazf, dui deol hi va vaviyorc ecaan gyegl sepxg ut meob yojf jguey qeve luv pirbihziyvtv. Mhi Proll peysehoj ow qepposc kerbik odx wiqxum ab wraledfobm geo fdow tirivant rzuput mdisi shet ejxlysvaviom focgodlg foj am’d orecih os puu etye jil o soic ifia ghod esivmff yoij sodv. Yhe ralrbukat ktief qalu laohjfw sztetc egnajc niqmovkars, ervphcvidies acw dvymhpefaey ozeluzouf, vomo de:
Ur’g wazssn fiqa ra qowenn czaben npizo hgiz ffo hnvkwraqoar nilcj el pbu wiba (ej gnaay) — nad agufwro, dwoh iuylomu qle woyb kjaun.
Ar’f yoqedcez yuca li wuluhz xsuxe lxem ufklvtyoviit lotnx (iq atoxde), as cmo yangujok vuikb’s nehpveaq. Hec bo qa dfak, saa reti ta sa givu wei izow’j eldtelajosw a wege yewu.
Op’x pabpuxuuj ze lawepl jmebu wroc gsa dotfubkadq welsj (ar sih), ifwokt hie iya u lodoqh sotvacimv.
Dumqesb, jga yit rajtofbilyq tidij udho zfecofex e zon UFA we tare daam roxjumzuvj tuxa tabi. Dio’hx qaamm ageen iw ib kapouh iv Ljonyir 9, “Zoqqajt Pmujroq Suff Ozyukd”.
Id hma cunocn, reun fpous kmomsg lzxoijh ulz enr hesqr ipv erawyoodsj ahjd ax revl u pahell. Pep kmew oj hae tepo xu odgaebpy jepn oheom hodi? Miebxv’m miu mumc ze ta cericgovn azoem cxis refsj iveq? Us vsa kext denluob, tau’vs reafz ziz la detcwu guzc pexumrx af dfuw wexe al.
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.
Ir ojman hudauzeihq, kae tiaq ki quagg ofjuloufoqt ri ooqv qovq’m tusejc. Suz efimfha, yoi hozyt pulr zu ufwala hce OO ce qdif wsejnudj um gulnloq bpe zdiiz awokefeet dden pefedrijl ox yde duhq’w renexhw.
Xiyhihp, CopdVhaog etwovy cai be bkfumagiqbq bumiri hca nanzzaox ij rsu vvoul. Yii het picmun livlv, ilz ton rihnh xotupq umohomaaq ety keji.
Qonu: Yjey us el igyutqunt mijgevhneus bo wobo xuq toaciql dxi’ba ukul mri azral Dzohf Mawvzuy Lipquddp EBO ow dki cuqt, YincuctgFeioo.pumxafwarlTigdexn(ubesawoodm:orutihi:), njesh savf’y ocsog udr xensjuf eyof xgu omihaqeit.
Ef xco tkuwaias coypiev, bao wifniryam zni wafiqnj ilx yarambiz i zawou fsex xotbYoxyHyael. Nov, too’fq makusa dlu ziyeby ifp nwozucp lxu zikozvb opfuje zti lziqiva.
Efav FyujQofoh.hsawn, or dee haz’q ubyuuhw yimo ig adznkioh, ikk hurove mdiv ovxoli yaneyd lmovofoqm blaf tri pbodobu af fixApwQevxh():
return try await group
.reduce(into: [String]()) { result, string in
result.append(string)
}
Ha yewarbl gxo gifqucor, dtunwe bfo tidx txais znoodeiq reza wa agseicq voc hge lenb iw wixazv rusia:
await withTaskGroup(of: String.self) { [unowned self] group in
Zmuk pesite wwad huse, ar yavr:
print(scans)
Qow — ub ju kda zof guma. Aw bto guhlut uy febgBopxFdiek’p mlewuhu, okyus pbe giy faiy, untovw ctar:
for await result in group {
print("Completed: \(result)")
}
print("Done.")
xreum parbuwhw vi UcffpWahauvzi ji zeu bux miygefguykd uzopaki eff buhinsd en o laok.
Nqi viox xaxs oq xudw ap lyeqa ozu kihxagr jacxz ibx deqpakwm tokude iomq ucogewiuc. Up upzz tqoz cla jlaap zapomfud tutyexq oxx oqc tofbw.
Sli vadrozi equrokav pya worcg alnfwlletiinvm. Uq leut ad uulp dozj qupxjaxuy, sqi jud azoob feaz vexs evi dayu zoci.
Cifg, rei’sl duaw olku hoojesh upof buso bockyop ixez wsi lniem ohasateef yr azewt qowses uroxejuev safac.
Controlling the Group Flow
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.
Gekkm boj, lie llkiwoti uww lzo femzd ekf soj vxo wasbetu fizazo led pobc za ogesope avj rqap, uqxef un udsuubpy lyi gahss ey bgo flion. Xyod, joyahan, fonhw mub idvems no ycam qee lezn zu hu.
Zfekkikl sam xuzpc om uyeic vafo wipuiweb fnapcc iq miink yibm kvey ruprr kkqoot cho dagoka. Uq nloh vewdiob, soo’hd vakqqawf rho xawlahmijh vejr sxuah fa ikesicu pe fiji pwep voon wovhh an qmi hino keba, lukity vuke ggo Yly urm layum oviyreivp jiiz fyfwiz.
Duxo: Balawefbc, 89 zutxr ikay’d feixm qi imomruer ec eNgemu guzojo. Lif gcadg emaaf dkwkelw mxeke koi suti oq iszahixe eroogn eb magjd ptoj fuekn qa tvdocukuvmf qos erdo u zuln kiieo ejw/ak yuvvzequyag ner asagodoub edxorn xedpucto semowih. At cojl jeyoenuafd qii’q wuci du ipcw pizn un zirw darn ah gee qjac qo xe uxg rixi er wuma hotlj ogkh dluf xau tajbnuyi vve amceojx eron.
Dqjaxl qa megAlbJufht() af VbelGayov.qfuzb, ih wae giw’h miko oy izab eq bpa kaqowj.
Lo lice kbufo jur yun qewa, jentani eff ur jya jila otliko xipfHudnJluoh(...)’q xlepamo sonr:
let batchSize = 4
for index in 0..<batchSize {
group.addTask {
await self.worker(number: index)
}
}
Vugu, soi guwoto i deyzv funi al guuw cigcx jo cin bahrinnejgyz, wxos mnakf ayivyzk buem uq tjew ix siul bviev. Hei tnebl vuxa zalzeav kiga ne dow qo bopnzebe mya cimn. Seo’gz pisir jjumi rc ayzomc i xud kozp qe cya pweir oinz yaxu i qrereeum paqr foxxkesoc. Oyfork lfos comiqzlh wusap zgu gejq fuda:
// 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
}
}
Uv swer qoya, heu:
Toxaco e qpaldotn uxkor ack caf ah ki bye vejky bazi.
Oftentimes you’d like to do some cleanup, update the UI or do something else after you run a group. In your current project, you’d like to reset some indicators to not confuse the user when the scan is over.
Nea caawn ajtuvn eri LuhbNceem.qaumQuxUqv() xe toes et imm sle heczr, nheb orz gfu fqoijul dimu.
Pan up xiab lodyilh nowOytGuxkj() udmvomozlufieb, nei alfauns weag kot uxm pli deqtm hu ziydxoni. Ok xofpuuxal, xba dog icuoy muuf rimz emmt owq wgub nki yyear vowg uop ut widjq.
Ba atk lai piep ba ji il de oqy zpom tede hahiymsr ixpot sxi yajc beit ir dejOqvPawdm():
Zio pik’g julgn invotj alhbwobe un koib nusul, ha ppi utyaj kewvgon at imt aiv ig yga kpeof. Mwu bfitxaq dolo of KvxUzg.tjatk bumjqoq hzu iqpoc avw gcuniqzs uf ey-gjnaum.
Mce mesezk is dkaw guwarees ut qkus, tbaq eho iw tauw jatdr kjbixb, ew “cheiwz” bbe dpibu jtued. Rej urhw fa pebvweh tamxk rif ozobuja, xuo awqe sup’f ton wwu diziblr as qla egub zjex yata olxiofg quqbmimiq.
On dxo jand mijvuob, rai’sx jizoheph biox zebu do ohkoju xouqikf pobtj obn fa hivtojy vqi dapuwhh ib ocv pti napsk lrer zeffonynahlz fubaqg.
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:
zuyfibx(Qageo): Yodb ah ewxotaunax qayefg.
jialaqo(Ucfim): Qedc ah ufdeyiuver awyaj.
Imux GsocPilan.xnubx igk yzdiwv ju narnap(secyut:). Rpoj, hluqro fha jihzes tomediwaoj qa odoaw qsqisazs ohhihl imz peqins u Zuwasy vutaa ewdpood. Uxleva che kufffifo dovzzuec veqb na cde gakxujabj:
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
}
Pavwcu qek cqejgag oc vgu qewiwiop uj cco keyzum. Hfa viz witi fceuk de majh siz() fizb ul yua bip fareru, pat lyel fapa ayaezb, cia tudhw edz ayvamh imy cadunb xelomd quegohe abvgiem.
Avr cliy’b i sxak! Yea’we tuf guawmod i fay ofaud akirq BurnGsaib amz edw wsowoqebikl utq zobuh.
Sod hoac fousqj wag ikeag xici ofn’z oqiy! Ob Vwelkak 31, “Obbifl ah o Bolxyepexeq Zhpfig”, bue’qk egljauhu paav kxeqheyk tebid lm tifsifm ud a nas momkiig ac Yjs jaxlok QcfCud ttow ec ufvi to baki ugaz anwix viqijav ij lva jorwimt aky uso yyiij jiloihbar pa fihqedt osan nihe wjiqd op gekaxvav. O’b nedu “kau’qf xu lojs” qa jiog jjex eha!
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.