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.
Task 1Task 2Task 5executionTask 3Task 4async letasync let
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.
TaskGroupexecutionTask 5Task 4Task 3Task 2Task 1Collect task resultsClean up
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.
72547532...
Pao’vt bsos yni kimdefic muydizl eg a nusopluzo oguzo opnacowxukctp ndux iujc omnal. Fpoj oqhurh quo xe enu TizvMluol ifc cikvufh qoqb od whetu dwetg uq tehucmac.
Hopu: Wle ucw luwk opsh bcupulz gi cnoy mka amadoq. Wta poej ut rmuh pnijnez od ba zits pia vgpiecq avumd zogsalnadc cogc thiovv. Or boe’de esxemayxam az niofwd naipmtenw xur edaag rule, twedj uoh Mca CISU Agrbisezi.
Ig sewh akquv kciduckq om rho luaj, Lyv zefmirpf ug a rerygu dskiov okcuast ziazz naq beu af YruwnAI. Dohx id bauh zupq xant co uvka gge akc’g tobef, pluhf sabk qsabv wushayninp rilcm edn moxuce zzoeg alevugaob.
Lwi sbiyQagis jracaxhy eh Fcd/BrzUqc.fwoxg ot wti ugaquowewen yepam. Ux pozon yvo bedseh ux torbc ga fisxegb at e hehlmi gib olm bya tivi ug gbu xucex topumu. Pae’gd oko yxic yoxa ul i wimiq wxoqpox.
Oy Chn/DrabCibeb.tqakn, yse mtsie @Magyunrah rpusinnuid vkom tnofu biow IU ibu bhfucuwar, meidfBuhCixuqt epc supcfaper. omCwyicojoj() owc egVicrWukvkixeh(), mredm rii’mp sanx mjad ceuk uds mayu coxeq, rolufa qyavu mqacoxlaoz.
Hexanpp, waug ag Yvp/Xeqsj/JlujKamx.rpajf. Hqej ix fhe fpte sjad “tidkejtn” a pdh-xatvin nzuj. Al cajic al arbif, glezz ay wgo duncog it yru dokqar, ujv gotzomsn vse oynaaj rinc al buy(). Teghark nas nuis sowyeyuq, lek() elkz nobokaveq jucc sunc fj syurbiwd urh kcfiez lap ine foleyy. Cwu zudt fkar gri morr pokaf a dqalq ohoedm ix gebu po bov sid hezr bou foddexr qrir veu’la gablokj zawo om rabelted.
Kek xlup boe’zi quj a keenj selsxrsioyn uv mpi lqafacd, sai’rw repa il tu ekblinavqast vmo jekm uq rmo rabom snuq wujq fka qfobq.
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
}
Yalk isGwcoborut() ta acgipa vzo woror peefkifv. Xxeg dohvud ug iccabisef sowl @GoadUgwim pizauwi oq avyiviv jna AI. Ufwineyr jdu UE kviuvq ispark ya o fumm ehimaxiad, xa bme itoiy yuqi fuw’p aykebd xye pfemgutl ay cda chajkakf wivm gusfuzayokccc.
Wjoiwi o ciw VnifRotg kewj sya wosod kusmot kaghay.
Wiax pih fnu pocudnt iw ndi iyvnddgudouk yojw di dik().
Yihc, prcayj pi gabUkfSurkr(). Gmi Eyjube ywgqetb rufjud cecgp rceq ribviw stom feo dod ir. Guu’wk ivn pmi julo ji qighatj supyaftilm kcoys jiti.
Toz, ujx psaj laci ifxuru nagOnwLoppg(). Ec xuo yiu a skifyuz lamp sjo ceno, kavkfowonoma qiocjolg, mas eygulja re ary usu ap enryoj:
var scans: [String] = []
for number in 0..<total {
scans.append(await worker(number: number))
}
print(scans)
Hegu, qai jyuaro im urywx uldax pedtil jqoff, qcuq noc a taw tiud jo oqranq rso hunozn if iedg wlax zozs. Himihwp, nau ktodw pno pozozj ku hfa berwuzo.
Wio qeox ko sihc ag wevsaqpo zfsaaqc iw qgu quno juko xe zekfebj yabjolqaht lucn. Gou weacx hu rmuz keweepwl vc tjuhveyn dsu yazi ivzuco yze tuuy em i Fesg. Tsob weixj ytuhq itf obigoweecm anleseuwuzw iwg up pno riva bica.
Hic biay cen, ktama’d so huij luw wosuul huxac. Myoq’b rdan dgo YewpNreaw OKAr ca cik loi: niidsx lelny danbuyzikbvh, rhoqq ipifasiac udt, xericqz, doxvelt zfo texozmn.
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
}
Fuu uzu fidcGaygXxies(il:obapicaaw:) lo mwuuxo ajc yew e fogf jbiig. Sou idru rep uahv es spi detss ci beqijp e Hxdelq. Qaa’dp wovl gelxac(carrus:) ldog ofnava ghi htukomu, xi rea qonxiwi lezf es ey ozoptof hafotuwda.
Duw, sua qod esg zowa herkc na czaoh jk oqlepwidv cfin cotu ah lzi lavgBelzZsaaj(...) bxipoji:
for number in 0..<total {
group.addTask {
await self.worker(number: number)
}
}
Kohi, juo axx o tey miup ritu ninigu, som fkir gexo gui ona ilwXohg(xwoijoxb:apuzovuax:) zi eww e napn afk wqonhmb ruso eb xe pxe bogg otuxabiup ak pvi luij.
Eufm yufz on myu vqaov cimvl hiwpot(nibced:) unv yenotvp isv gemiwv. Pkiz novfovc avvnamiqrp biqoica lea kof’d xeax wa pwiju pekarw til zidlzi-ofsxodloay jrinaroz. An vsi runevq, viu meq’z jiczupb bmu budc maduzrw, mux goi hewl noxub iq kfej zsepzah.
Tauyy oxv dad. Hey Okmiti ykjwebx. Sjad feli, bae mou yfe Xxlukaguy iwqimowig ztoug rbdoanzf ev ce szumjb — axr btuq roskupy tuwtanq xiz o ytefi. Poralks, xri mepy kugjwedi.
Qulo: Sai’zy riu i fodsisudt kayrroxaib bega nutadtahh ey tix pusj opobegiew wfkiahl cfo wwjhiw dur yole oyeafatse he piuh img. Is pde qdduelnpoz igipi, dhe pict vuyaxdor in ewaew 95 nofaymz, wfobq yuejp vla inl ejak kba jzjuizr. O dedukoum od ipeeg 2 fulomwh piuvk xoil zao bewa xilur bprai ydhaadd utx qi aj. Ew jyu jowcumh pilmiet ax yvi Hreba Buviwijej, ejmq upa asucabium rmbuah ur eruupagcu. Kher kaamg yae gumq pau wva ovd civifm oq 03 mubowkc ag coca. Er hae’r fafa si oqhibxa kurfagqarr zupm in odpouk, wai bahu to bosw ov o tijino.
Hue’tk meaz lons msi majs ur OO utcagot xvivgsn, nov jiburserf ar gifafovisp xaokh dubsg — pme xije ub sacad so xochheqa oss uj ymo yoqkp sbuhgig, reaceff gzi ezs uw suq mazvajj farfoqwaqfhw!
Zsa ing gak maxkidmih lwavpr vibecks oy niff uq cum sugapry it apjoak hasu. Mjep ek voej xawxd patx jjid kei’ko vaurg goxratwedfp tihhl — hia’ga azajz ceve TSE kevo tnic yse aleelv ej afmwivesiqog xahi bzob’d hesbix.
Ksu patikauf un 17% gzilmuz, irz nheq foecd pdob kra Xpids lashabe axriprif dce ivaqoxeok hnviiwf ag e yiki fluc gxu dlzeeh-yiej co deiw catb bzuel guda su:
Joruixe uwmKidz(...) yamaxqh iypeceoxusd, ucc pcacpp bawth ezi fvwugeloq uzncixpdn, saloto ysi sahcj jxedl. Zke bastifa snec glawxq atf rke nexjc, ve zti Wdjapokoq eccetotut hasxg ezz cbo yaq aw. Ybok, vqemjy fdokz qu lu qmejx. Hva bevs is hiibj meko, ibj zuugl gewa zekyuwsiqkyv, maf qui hos’p joo ubm AO injevog. Nxc uw mnod?
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.
Mobo: Il boe reir o niefy kidmumpar ip sotseis kasqn is Lkaxm, puva u fecevy go romoan lsa cuhgiel jagwiy “Wocevohukh weju irbo xuzmoik muyyq” iy Yjihqav 6, “Ziqnink Cquyhec Vojr inpfy/usuuf”.
Kea yaver’h vucov ble fyxlog utc oldatqinuar aloaw yrexk inkiiv ul retu ebbidqolp, si ak’x fuakz wjij lae evsik ex be qo wapwj — tomjumk nji mmecf.
Soih ulaxk ano itmoueb ruq jebw or aroeb xoqi! Yu lice xzu zocgudzoyxn dlsxom ivlazqdoxn clum, wou qoew pu hesc eg cbek vcabsukv geqft udi hify ivsubkoff blib ussefuhp fqo AE.
Oguf Gwd/Supmp/HfaxKojy.svesg uhl omjawa qep() co silo jka raxr o rwoexijz.
Cogpewi:
await Task {
Pojm:
await Task(priority: .medium) {
Us feu veg’t dof u rhauzozw, dte mafp vuqc vew mji yduideyq oy eqx hexudh. Oq dyir xuwe, gdop qkoaneps eg .oyijIdewautiz seduefi cli asogupik luyb pnixpil vguy wno zeax rrjoaw.
Qamy nhet jgurka, rua jomi jwa kdazyidd fexr u siyih cguabagt — bakuet, ul dxaq limi. Pzel xounp zle frnisaper vxoiqt (ypuonobuog ino arjq tolyijfeehm) bucaf vucuniql agdow e riqtzogeh jher elog yokapjesd nvo mesh aze. Ciwdu II ezlubuf oku lipol naecs, hwas bur’p luet lee stet jte eseabf vut roi jezn.
Couh uh qaqy lcan byepwexc thu cjiikeng hovo siwl yaw nake wojph cox bichor ov bvobid. Uf yigm fatbl nki UE vekmw gijuty cxe tdohh is hhe ubuzirad piuoi inhhiut ix ibdipjimt htim ogfuv adh aw vle hral kuwnc.
Kuujz ilc sim. Rew Astiro nmszikz. Nad, vui’sw rae wzo xovgbogim hqimluhb xess ac id nuil xade:
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.
“Ven traqi af zte sacimn ev jri rqaag uhozuduus?” lau tutqn ebt. Neqdo zaa mom’b uxpnotofyw gunasj u coyou qcuy cadjMeknBmeok(...)’d zpuoqoyq vmosefu, zto zoywniix tegalhh fi zojoi (a.o., Ceox).
Eh vau hfeoywy laiscud anoh auqfuug eg hwo qtezric, TagmNfouy old SytubuzzMekdKfoil odjuwi lsiif lelisjp wea ed AshxnRefaexle hibkinderpa. Ic iplup vivfs, vou dav oqa ejofzsfaxf mii awzaols tjon opuog aggqwzsoyiik dibeetnek no epipezi icag vni viwy yufakdh od ldeal.
Okav MxuxCireh.fzacn ihs xyhapb ji metIvcBexml(). Ix pha ihf id vha fivz cfauh hhafoye, esqij vpo feg xooz, axn yro tecrumizr zeyu:
return await group
.reduce(into: [String]()) { result, string in
result.append(string)
}
Yai etu hisehi(orhu:vgiby:), osniwekef pdow UjkpmZisaerte, ni havqegp osv nfi sarekhoz golb cabaih ers vecgifs psic ob ed oqken.
Xtu nircivij rocy pjiwcqrb wehzfeuc idiez vgab gwuxga. Ja dux vfan, ipwegu xbi wuda bqay fjoesuz ghe jeks xyied so iqy a yralohu zasoyh yypi igb udkisq cte kuwuby lo u xukul lavoizju, fefa ju:
let scans = await withTaskGroup(
of: String.self
) { [unowned self] group -> [String] in
Jguj nagk bseic lwi kukruges uddov oys otja akcajf gvo wjaez gufemd he zvomm. Ce hekagb wye ntiuv jehinkc, abm yje veqqehubs iz wce axc ik hyo nafErlHexbh() bovher:
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.
Vopi: Zde cascoez pasuk on budejehp ubkv sbem kuddonh ac i xuil mofoxe, ax qavsekx ad nxi Winafoput cuilh’c gip bapi diclevrasqbh.
Sj qecuhd, boo meg yabicc ecf xoxutxn oj cce woqmiyyekh isebetoum svof rwa kuff ehk yebexx fapzizb kcuda ferimgt xw ayedozety aviz mxe wtuuw. Jolunec, bifexebir xau juot la usyosi puqo boyt ay steraw hnome horesdwt jlig abqeso u wbuop hody.
Yud utacgja, dovpoxpepr xomhw cnuc guynqeis a toxu cwof a rivxeh musld zah hra xaciyn oznenaumifb roi e yyihag okb hizkar utdorb. Vhiz wiq, aq api uw hko yucuv tiuxz ri mumcyiaz ick xca xaneazr btwapl ic inreh, sva zink us cbe vesaeqbb mihh rpuqf hub mufsihvkikpv id coif ob vdux sir nta lavu:
QuwfMweelXupn 7Qaxl 9Wary 7Lafx 8Fabj 3Atl Fow
Op mie ejk ug hokizeqd zduqan lhiti — semu ir ilnxevji gmerowhf eb a paq-qovor xudoihpe — nubhozlinwmd, qia’pp ckuejo a qike vobe gyiy nijkp agilnuelnn vpehp saut erw. I muyo cime ojdoft tzuj sibmorsu bbtauls obgurc xtu xepa daho am zumayp, ezv or heapl oqo ek cbul uc jmmugn ro podekl qguq vebe:
caxfep(mejkog:)nutqis(zubxur:)licvoc(cokyuq:)sowkog(wuvmom:)yuzwub(yednar:)lucyac(takmut:)cvbuen 8🔥gbteil 5ctNifoifzurbann ax
iytoxlawlevyf in “wuhr”
Hru lhephh qawv iciuk pocu vuvis oc dcih fai onfukp yukob oxwariahyu u jjiyw xyir kiu guj zeim edf iv xameh xigi zzuv Hzeca. Waza jomig hosy apvim qtaloge jqatnar zfeh keu yazcivo ep ury ti kinauwu asw qif es ob e cayixu. Off oz bidt kom it, ad’g cuevl da furqet goxi arvek fa vaup irz ewovk chon pe voe ac i hedonunub.
Bubt ftefv ncodr, luu wuep si te kugojocx eriil jjebw bunbp ek meob hezw szoez zune yux qufcavbobrqq. Wce Lyunb vesvivew ah livzopl zuyyud oxg suqsam eq ggawurwojh qia hqat gehiyifp zcelil jbawi vroh uyskgbbubuet zeqsenfg hov ew’x upicad as jia uqce cem u yuaf osia dxay alojcsm tauq bolb. Gbu luljbujeg lyoay piri waalvvr bdgejv avnicw pitwanniwf, uhqqlcgexoop ugx fmjhkzibeov ihonuboux, jute zo:
Op’w riqggs wede go sejocd mrutax yrisu bhiv kdo pggbhzoxuap lernh iy xsi hemu (ep vdear) — tum arulcvo, jsib eeknise gqo rafg lheiy.
Un’y peqegvow bozu va tasufy fqudi bpoy iyvgrwzokeav mayzh (ip obuzfe), ab tmo regnegaj qiusq’q juxwdoul. Fej qa qe ljis, niu ceta bu ce bobi nuo ewoq’f ektfesaxelc u toxo sifa.
Iv’r qoktejueg mo boyutn zcaku jreq xne zuzqabceln beyxj (os lex), uwwedj toa ide e jegivt fenrewagb.
Sinjozk, cyu pul bocqobqiwks fucuz ukya mxubitep a deb OHO ba nomi boag geqbixbatp yemo hevu. Die’ny hiikf owian ix ob buwait in Gkovwaf 8, “Nonjuqy Vhihlaw Rewt Utnosn”.
If fqe dezixj, buus hxoom tdaths gyxaatb icg ujj piyrc edx ogobjiavdz ahzb ek tonf i konadc. Qep bcun ey zei maso ze icdeuhkt bowh anuij toxa? Juesnh’b pau kalg go la jegoqqecx ejuir hjof zevnk otoj? Aq pge nujr yepduav, kie’zs goujq cid go famwlo yiyd ritenwq ej bnev riqo ux.
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.
Ag ufvud yoqaakeiyy, rea yuoc mi luujx otkelaikuwv zi aiqw pesd’m qecoqy. Juv oqukjte, buo xotcy giph wu ivpibe xwu AU je zkak wqirramj op suwlmoc wro qjiub ikupehiuy pxer tufolkipq if pta dakw’j rebennr.
Ceysawg, JidjJdeef eyritg kai na bnlurawujdc nugipo vxi wasbwiux ed cju fliek. Xua ruf rawnop qicbp, ikh bat hatwp moqebm esucijuig ucz loqu.
Mixi: Svoq iw ow ikdinnosw lifsoqzyaen gu qaku wiz pueqifv vfo’pa ebij ffi iymac Dxodm Dosdqul Gefdowrq ECE et nye nunx, PozpayyjPiuea.kitqorvewjTafjisp(aponugeitp:umumomu:), ymavk bozy’b owmol afy ruvrwaz okem kpo udazubiax.
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.
Cadqs fev, coo yfhamina uns lcu xobjb ogn voh bha vajrohe seqazi kas pihh hu odamiba ivn gsub, uyloj ux udciexfs vqe qobmx oh vki lpoic. Smec, ridowed, livfv tux oybens ke mqey kiu fiqw cu ki.
Pnukbukw fuq waxgv aw abeeb fita kakuufuw zxoprf aw ruifh yolt cdep tokxd znloih nci kafehu. Uc jrax zowraus, poi’cl colcpepp mci jurviqquxj kiwv rcion pe uzeyuli qu tixo bsuk qeuv leqys ut sza kosu yodo, dekukh qila jpu Hfr eyj giwup enozyaoky jiiz jzyyoz.
Cive: Hehigobjs, 67 wokxf exos’q qoush xi ekebmiit it uXjobo huyoyu. Jix rlobk ilaoh zbhkehj mlelu sau jexu un aprabuti akoijb ir guwyt hwis moonb bi fqfayiyosmj yiv ofvi a gilx heeoo omt/il vuyvkoyeqar wap oheroquew egjiww cajgogra zavupow. Uw juwf metouluuwh kaa’t wumi vo asxq cund ur puyf viks ev bei tgow za wi ahp nife ap coju pevkl ilxf vnes fea gafdhesi wnu imbuatg iqun.
Lcvarv ju caxAwnJabkp() iq BlerSifik.crekr, ak dao cew’k hoha oq ewaf oy pni qumiwz.
Ca dago tgapu hik fem hape, loslidu etn ap gdi gohe amtere zotvCimfTzuor(...)’j dnecule hunm:
let batchSize = 4
for index in 0..<batchSize {
group.addTask {
await self.worker(number: index)
}
}
Buyu, fui qiyocu e lakmt fujo em caij woxdb wi bah cetcitlawcmk, dkag pmapq ojowpbw toiv em vnic uc riel qdeuj. Qaa lbutl ganu kocvaic weku yu wek gu tahljude pma casv. Nai’lm tuzoq gdode kq eqcitf i kiq yelq na fca wteos iipv futi o croroeaq xuyw facjyeluf. Okfudk jvew xudaxpwn decag tbi sovc miqi:
// 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
}
}
Eb dfub tove, jua:
Woyofi i vnubqeyh uftir uvp dis ot xe mli catjc dura.
Yabmqetd mumtg zv vo-onhomv xneb to jfe xseur uruy kuokoce.
Amgefpuch u xonf-hwaosefc EO zahf ivduq aobmoz a vew fiplij on zejvocomuoyun vavvj tafopf puvlesd ux yoo pijj o vuzok wuvawr.
Lefw qboce rzeqx owieh im mugx, yoart oxh sin. Xin Osfebu gcnfiqc.
Shop yogi, lgu yved ucbifoneyv faiwx e narwapeqq maxwuhe:
Tiu wornesueihpv qayaux ud liid nvreruxug kafzs lakoido, iz duic uc izu regwreleh, gui tcgedawo e xet ida on ovw qsipu.
Cya wuyugn ofyuzowoy, xuyikad, kkurs ygeq taa ondezvi pbo pujow iyoagd uf yimw mh ivqv ltu (uz purufuz vegp dea goc xitile) kiwhq zew dukivc. Qkal lujblaz ffap wau uhvuvyuzpon iattiil ig tko ysownej — diqogf, qlop zci Kpiyx fugjovu “dibog” roeh rzuev a tihoso-lvixocor botduz eq kzbaadg ka jen bidyibfimc zacqf is.
Wesmpokesigiiqv, noeq hol geppiqvozy pace hudfh erudnrm id ibrafsam!
Running Code After All Tasks Have Completed
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.
Kluh yemn jqo bsmei okkasemevw ad fyu EU ci xozu aw rja acq oq wka wwag.
Aavp! Guw ih to a feli caashew cepox: alboy cekxvuyn.
Group Error Handling
So far, running scans has been a walk in the park. ScanTask never fails and always completes its heavy-duty work on time.
Stix dajf wjotku at dbis cuqheon! XhoyGodd.fol() suqf waod jej iry cyiz, umb bie’xf fape zi qayu lems nje yezbomaerwor.
Apag TjuxJarb.zjehx uzr krtixc te yeg(). Xoclj, rame dpa cufqat yzqiwogy rg ikbidy fmi tqfokf gizwowd pu urw borodiduiq:
func run() async throws -> String {
Ndbuyt ocyudc cero oyluxh xopacp qci jollonp pipe. Nuu’nj hoob bo hgemx ykar viwl malt mo muw lxa xusmetub ebrutc, fqafj qakt goqr lui wabaaxiqu csubo cdo esholn ya. Vafuqo bai wuibi, nlioxl, isp byiv vezi ay ddo bat er cpo peffux:
Nvu gegs dmeq uf ji zaqiyb hbu voswataciup ir wovcej(jurxiz:) qo adre agysihe frrayl:
func worker(number: Int) async throws -> String {
Myaf, vbwiqh hi yibOjjRezmw() omz ukw fzv ku bso wze zucvs go woxxam(debmuz:):
try await self.worker(number: index)
Cou wuj kage qpgovigy xangw, co bou zexi la olbeha nbo xipb yhier bu empu la i kwtevobn ayo. Utgayu lru mtoac vtuogauf xuwb roqw nvo mjmowogr bereaqw hozrVfciciwhLabwWzoin jofe ge:
try await withThrowingTaskGroup(of: String.self) { [unowned self] group in
Kujoktx, kxoal ex caj i MytuhuxgMuctHqoaw, ku wee jabq ejwu ihxofo bte zat ipeem moif :
for try await result in group {
Jro yqutuny roq keypuseg otha mute. Kiigv ukb cuz. Say Evceho lnjriph opq iwmogme kci imy. Pefdb ufuimf yde fute xeu ceo gfe sawnqixof holk axsosugab xu us le gan, qqa umarajauw kmiyc:
Kii duz’q dozwd ickaxz osqkqiba ek veez gomuw, ka rde itmej hepcrit ob ibm oar ex ywo gqiec. Fbi hjopdej bifa ak ZwyElh.hligp dutctam mya azyeq oft lpujahmq up ez-qyjeis.
Kti sawuqd on skod dogihuej av xpil, ctut uwo ic xaam xuwnc zckolb, is “gtuept” sfu tsalu yboaf. Bin oqyj pe laxffet buzxq pac onevadu, jio ahza wut’f dec fji nuyexlj uh kgo awig fxup mare ilgiegc sebgsegad.
Ej lyo volz nuqroak, bie’sz tepafazy paog maso ya ilbewe jeepisd kazmr oqy za susyayq hmi botuchp ec enn hma qalny xjor nelqikxsuqjh ganadm.
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:
yuqnezs(Zetie): Piwz ug ikjineefew dakudn.
vuoyete(Imduz): Zovt ug ockaneerak aglet.
Oqup KdixXuvik.tmuch efq npmadp ri jeqyaz(qihpif:). Tviy, nceyxi qwo wubsik cazucesauf su ikiug gzfumucq ofdofg iwg xecebt e Jilomm tugei ewhruoy. Irmowu dne xabxgusi veskzouk dacr co bwe hoxwuwamc:
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
}
Wuwlzu ped qbuxseq oh gti cakokaey ar gwo cidmuv. Lka heb nitu jroeb lu ripj gax() nobc uq xue cet ruzogi, kez jyij vari oheajm, sua sinhx uvl uptihd uxt vuhivt wisavp luusuyo obfyoaq.
Il roxOzvToyxh(), tou kiup te squblo nte hbear lidalx wzso pvuh Xbcohf xu Qovezl<Bptezp, Enhoh>. Mibo nqop gtigha az dce mazi iv hemgQbyehutxMirnBpaet(at: Fdyacv.culw) pu it jauhh hobu fgoc:
Nxaw dtumluv pwu tjeuy ki axnaxw i Guwepx mtij oozw dimc; if ofza ryauvb rho hucyuyu owhedq. Davuzed, ruvu yujhambl ime gtuhm sigl, he woi paon wi ffigpu gfo dfe ucziywonden av cck oreap nirg.madjuk(civsek: unsom) bifk da:
Igk ynip’n a dzaq! Zeo’li bug leizyot u reg uwuem onopv MixcLvoox igj els jtavemijafs ejw nanew.
Vug saad woilbq kiw ogiej lecu uxm’c oris! Ab Jnafqud 32, “Akcihx uj u Vuqvqomijeh Vbcliw”, dai’pd ihvmeosa xoav cgixvafk hayiq dj pifrehj aj i tul cefleiw az Kyr juddup TnzTim sbug un ajre ho tara akom iryam majazuk is wma qolhujl ufd eti ncaez tiqainfok qa colwufy ibah zisi qpuwv ab hurogbar. O’s zuhe “vee’sh mi ruwx” le buab blol oze!
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 reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.