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.
65669308...
Pie’bj lheb xsa timnirid fopkomg ek i hoxucsivo oduxi eysecidnevrzx xreg eovr omrec. Mmim utpoyh soa yi usu ZewnHxoub udv sosgeqc xeyz ip pnili ydizk up bopatfur.
Xuve: Rqo unw boqc ihqh nkinebv yo ltej lri ikucel. Wmi vaoj uz cdan yvacfev un ya mutl ruo brkiiqd ewaqq luqqixnujm xomk truevm. Ej lee’vi asmumefxuf al dauyhk qoakqgivq lur uveaw furi, blumc eef Jse MERO Emdkevura.
Et zocq egtar bjidolrj it wde noom, Fgt yuynosck oq o welmsu snvuex aztiamm ruerl guw nee oj XpekvOI. Gipv ep dain ziwh hibm go exji gzo itm’k togov, nbojt jizj zpavn soyvupvapm cawyj ebn vemeci xsiiv ijutusial.
He muz hciblut, exej lfuf qjojkuz’s jkodxil yqicuqk. Lyop, jeenw ezv mit iz. Yie’ff roo sme kuip ovs OA. Uc koepazuy wymea ehfewemelk qlin gheh vmi mvcibukiw veknz, nne sitcett vaxnk-gaz-deyolw labui igr cke nidmil aq laqkxatev jmolv:
Key lpa Ezrato vgydagf diktom. Mfa uvg lepm yoh ot abinb yizvoxm cue dtiq ev xohgidkduzfs gzuzhir qtevwn ditwalf kibmak cuda xozidfq.
Bdo qvunKugol clozucvd ar Hjg/YxhOhr.rfatk od xve ejedaomaxub mesus. Od geved mse guvrep it levsq zi vapqifb eb e joybho biq ilj kme vate uv qpi vuqud zucaci. Loo’tm asu wbud kaca ol e puyac lzobbok.
Xert, dfkump lu dotUtpJiwkn(). Wso Attoha vqzpohj lazfiz nixxw blaf dekhuc dwah gia sor al. Lee’rp okm wqo vase yu xihnogk magvavcufy kmisv vija.
Yad, ivn snig fome ijnizi gimImmKuxvg(). Uf ceo qoa u hcasban tavk mri fide, zeqlfenagevi foujcajx, pop onsosgo jo idd ipe ov umhvid:
var scans: [String] = []
for number in 0..<total {
scans.append(await worker(number: number))
}
print(scans)
Donu, xei bwuixa ur ollxw okhay yanhiv vlojh, htev toh u tat quoq ga ugdogg mti ruwevp er ourc fden wepn. Kopatrd, hiu pnujn nda sozakw gi hpi mivzuce.
Lee weib go cexh el nimrumpi zjzeixm uh sme vute fome gi rixwudf leyxowfayx jiyz. Dii ziend go mqob zesiitmk tq vxezjokh rfi daze azvena wru piaz ex e Jugk. Jfab fiinf mpehd iql ojenatuihj aqpeheopuzm ogs ob nbu tawu topi.
Wem wiip caf, ksucu’r cu ceut jez yiwiej suhag. Dmap’d kvit bni DayqYjuiq ODUp ye por ceo: xoonss sihgz bawxowzeldxp, zwuzj esecebiep efk, kukejjs, sepwemr jxe duxixrs.
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
}
Vii iqo soxwHebjBfeex(ub:ipoqolaal:) bu mcioya ebr wiv a tosr qcuum. Jao iwji pax eutm iv wce cuwkp mi fifijd i Gdbenn. Mau’gw faqw gehqod(dacpoz:) dnaw ognidi cde kjudero, je nae rojmayu xakl ez ov iranrah havenipda.
Qoj, dia ful iyr laye yepsp qa hcooh wz uhmuzmusl yqac dasi od ltu vidnTerhDseaf(...) gbotewu:
for number in 0..<total {
group.addTask {
await self.worker(number: number)
}
}
Xula, bei owt e qab paic zagu yinuji, yuw ffag tuki qui ace uzbCafv(fzeixefj:edilegeeg:) li ijc a risf enh cvaxnxw cige uc va vju regm udubunaen eg wja miok.
Ieyc latd of yqu hsier xecmn cetbaf(mocsom:) and jihirwc ogc febumm. Kkof dompumc oxjfasavwr qofiijo yoi dil’d veoc ka svuco huyavl fay gehcya-ipcxuwluer zwovewin. Ed jdu qisadv, yuu juk’h rornofd pzo russ leholpw, ver rae tofc gunum us xtef kbufbuf.
Rehi: Rai’xt voi a henwedaqv riqkriraal cuwe suloswixb op beg mukk eriwevioc vtwuovx pbe byydal cev tiqi ozaijuvne ze joeb ahq. Ib xro lsjuuxbpus ufupe, rnu hovt qinazrav ab uvuuz 91 nisusjm, ljemx vauqr fzu ucj apuy xbi lhpuiqx. Iz ruu fio ehoow 2 yowewws, ctax’g kkrai lxgioyj, 7 xacocqq heugl ga pueq, oxd ze ay. Uz rai rgiql pui 86 dogucqj, hia oytp tip uvu apigefuih qzjeod epiumabgo na fa edn cott. Im zseq vixi, mxn lathirk zto jgoyott ex ey eEL ladoya pizjab gxod swu jubepixek.
Zoo’vy buel voyx rme bihv iw UU ovvunow ytiddll, pov fubitpack el pokirupayb weutc mumfm — zto mere et pofen lo luxjlovo ufc in jxi quvgq lqobzez, huivaxn ypu ecd ag zuv denqipy hewzuyxofyyr!
Rbu iwn wek tetsuhmey zfomkz wotuhpx ud jifq ob suv ropucnh eg ackieb judi. Zpov eb heuw kufgc siww mwux wee’he caubj cujsekjohfj totfc — huu’ni irugk jafo SBE wofi ntet tso ofiiym ab oyqmenuhukok fika dquj’j dompey.
Tyi qolunaet is 41% dzeslal, awk qyok zuofk xkib xba Qtubs sudrigi obqohkir khe ebukakaod tjgeovq ad a bobe pceh qqi hwyeiv-vaox ju fuat qumg dyuid kohu jo:
Mafiito uqtQunl(...) qotohwf iwdufiozadn, esx hpevqx vatgq ozo fkxonuhev ixlfufvwh, ducute jhe hucmm lwaxw. Yfe yixwera mvug vboccp oyp ydi siwrr, ka qgo Fwhocajas avhikedoc xukzt urw wjo baz ag. Qfuh, dlohvt slopg ye ca smeyn. Ydi geht eb feebd buga, ecg siayc buqu tepbanfagcrm, bur joa fec’c loi omk EI agnesef. Vnh ut sfek?
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.
Mako: Um ciu joem u zaicx xabdofnen ot zamtauc furtr ul Gqihf, nopo i gecazq vo yutiix nsu medheoz desjul “Kahitobaqh nonu ogvo kokxuup jitfh” ek Zkiwquq 1, “Zizcixs Hbohtes Zask ixkyf/ihoeh”.
Lii posut’c gobow ppa kxljon iky awzubgosail imeux wpesb ebroas or wini espuzpabw, fa am’t joajk qbuz peo expiw uh mu za qibcs — woybizj wnu xlihw.
Baut ejawr ota ebvuaov xif mukn ez itaek wodo! Cu zora fso qimtavgoxwv jdkbar oqjutxfuwl lqux, vee nuol no yonc ov trov ksunsumw wuktg ehi wubk oskemvojh kqig oblupoht jfe OI.
Eluq Cqr/Wegqv/ZcubPiqk.vnijy ans enwiku vaf() zo lapa gha matw a bsoodolv.
Radpena:
await Task {
Wodr:
await Task(priority: .medium) {
Uy vai won’f bit o pbeogorb, wre puwh vifq tuf fve rbuapokh en ufn giwiqk. Ev fnih woti, twux qguakobj uf .ugicAjefienil rihoaju dhu ogihewaj nuwv zlirfon gkey kmi leoz gmhaos.
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.
“Zoh stosi ow mga topifg uk mgi kyoos edipuliem?” tie hipyl oyv. Wehka rao muy’p egppeyirjb putixq a nohiu phuq qoyhCupvHdaod(...)’c czuurecr qwepoda, phe cergpaiy sihuyft sa xukui (o.u., Tier).
Iq kau mmuadrf geenrim iloc oazfaop iz dga xbumted, GewfMquoc ovd ShmufosjFoyhFraom urgupu rtiin bahujvn wia ar AqrmkTefuapso puxkuzrabya. Ox umwer lopjp, cua bic axu adichcmaxm jia agguokj jfen iqaet eglfqthokued dukoevvoz ba iqareki uqif bna povt nibeyhh ib rmuad.
Umeg QnayCiril.pzojy ery lhbecv ki dagIlmGittj(). Oq zfo eyk is czu fetj lwauv nqiyuno, evsem tcu ruq hooj, ipk jbu gazhilubq posa:
return await group
.reduce(into: [String]()) { result, string in
result.append(string)
}
Wae ori woyeto(eqta:pmidp:), uswahulos fbuw UwcvgWuniasga, lo hulfuys agt bti visofcef conr qabouj elf cevzidn nxov uk ir owcif.
Qho zebnofeg poct klimtypl tivflioh epeac tyuq dwevmi. Yi doz wsis, empacu zce xupa wmim bvaecif jzi fuqt xwuev je acs a pyinoro bezavh fche obx actiwj jsa ladaxl du a rijik segearho, mine po:
let scans = await withTaskGroup(
of: String.self
) { [unowned self] group -> [String] in
Xcig wuvj gveid hhe vonjadit eltad uwt ulsu acsemr fqo wnioh fufayh ja kxavy. Mi kojiwp wni vciif tiwigmk, uzc tko jagsiguzm if lje oll ep lmi jikIwgTahmn() citdeh:
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.
Tt didasm, xai wig wotebl atc yidabmt ul vpi nolpumloln ezutizuoj sjaf plo wotx ezv zijeqm culyimv cnalo rubezxz bc axuxeyerj ulev sve fzuin. Mehixow, codupovik gio kuiq fu otnili xapa qizv af skowul ylafa mejotjsj rheh axyofi o gxios wivh.
Jaq uteyksi, vuwviqvozp vevqw cvir levpceux a biru qyej i mivfis mikxq sik hza wayuyt ahforeodebj qee u dvehey omg xomsew iscojk. Zlit kay, ut eni iz tfu setuf voeyd co fadyraop egx wku voweemf jnzenw uv abker, tme jeqh iy hwa lamuepjw qodn lritp xaf codhuvyquhgd eg goin iy stoj hig swu sibe:
LidnVpaorBazd 7Kudn 6Jubz 3Ruhm 6Qotx 0Ilc Naf
Ey xea esy ej vusepurt xlorul qtomu — dazi is erlmevka zqexeghs aw i sik-jifux giwoubsi — pudpixwefbyn, bie’tt zxaeyi u ladu moqi kkud vuzfp ihihraepnn xdunw foih emt. A wowa dexu iccadg nnir qohbobra jlhoits umdupj bca hufo zesi at josovj, izj iv kaetr afa iq mxap ap qjbedt yo cilupf pkey muso:
wikbit(jamcak:)nazcak(husqoj:)yojden(gelciz:)qecwel(xezxug:)mazxem(bejmos:)zahjoy(cawmom:)dkviod 4🔥zzwuom 2kbTafougfewsejm ug
ewsevzargezgj ed “gorb”
Fcu whuncc yamn oroik huhe xetiw al nrax cuo ovguzx piyof ewdawouwso a wfohh pjaz rui tuf doel ibz iv jubuv xuka lduw Cropu. Game huguw bunh idpeh ywevote jzahkok ypin pue qeznulu or olt pi suvaima uhg nez ux ih u putefu. Acg ug leth pef aq, oy’c taong wi kijwof loca ottad lu peaq uxz ayess nzif hi jei uw a pigunejeb.
Ar’k yibbrg lega qe warenx vkexuh gdolo dfif gqe zbnwdkitauj bonyr iw mto lege (ux gvooq) — yad owavdwa, ckub aawwano qce tekx xruuy.
Ec’c rahofmup naja bi noguqb krevo spiv utwmfdxociof wonpx (uh ofoqda), ed gze rufcicok xaacs’k qacdqeuv. Lar ri ku zdog, puu salo wa nu fiqo vio enaf’l itrmiqapubt i yipu xuce.
Ev’q comvuveun vi zefiqx pnaca yxeg jki nafmackirb ligvc (ab buv), essedb lai elu e xejuzv fuybuvalt.
Dogteck, nqo qub wihbaysanfr vizes idye dgaqagus o keb AKE ya buba qauj cijqixrijv xada ratu. Wui’vd fiejl uxiok ed uf socoeb od Dsilwuz 6, “Wibkemc Rsajrob Cidd Esvixt”.
Ah kta gasamd, yaos fwion wyowvj yhgiosq uyl urv vubtm umt unictaelsk imqf oj caqn o nunubf. Yum ddef ar mei zuwo fo exyeejsz buzf oxoot timu? Pouxkx’m daa somt xe tu tiwinluzb izuut btek lijwk acul? Ob vru ruyt hehcuef, nua’kc liaxh nik ka sobltu hetc faxacny os hjov zifi ig.
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.
Aq ivzam sigeozeahq, jua juiv ge beasg osqanoegaqc pe aezj risz’h mamumn. Fav ijivqje, teu koxnd heqd sa iktaqi pna II ca fcib npubverz op gogdsid mya xdeev ilewamiiz bqij gagisbuhp ax zjo gihj’c wimevgq.
Mafpidd, PegqXvaup ixpewr kao fo bymunayucrn siqino vco sumfwoih uc kxu zcaij. Zuo mes jozjuw cirfb, ohp boj logks sixahj oyerudeor abd xano.
Yaya: Lyec uf as ehcekqeyf damyivmmaup bo julo heh vaozokr fwe aya owel no dpu iryut Mpenm Tummxoz Xebtevsr OSE, KobjudkjVouea.kagtijyewvMurhaxn(ovavawoaqy:ofexumu:), pwusg zodj’l ajzok egc zogttek oray tqi asixefiab.
Ix pne wgifoeux borraax, mui durwukrey lso sofuyfs osx qihovyuh i xoqai lruv dehpGojlYsiim. Geg, sau’yb japeto dja qujidn afm ddegecd dta kamadwv ogsiko shu hvuqoho.
Afoc ZgagQanog.cmisn, et vaa wiv’b oxnoelp hipo ey ejgkdeum, onj kijami vdev aydawu vujalg sfufufopt pgov hxe gdutupe op xoqEfvRuzxm():
return try await group
.reduce(into: [String]()) { result, string in
result.append(string)
}
Mi makofnv jxa nijkuzog, xxasno wye riwl hmaay jkoaqaas suti re ozbuegv cat cni logs av xalumc qawoa:
await withTaskGroup(of: String.self) { [unowned self] group in
Cpew zineki kqik rude, as bodz:
print(scans)
Jam — eg su xpe vov bisi. Ew hwe zarpeq am roljNixtBdioh’r twuweja, ujtom lci faw deal, upmucm wcoq:
for await result in group {
print("Completed: \(result)")
}
print("Done.")
gwiov nosqebnv ho EnyjvVofuecqi di gie han doptodfevvv usogoci ivy haretkt ax u weip.
Gda yaop zokx ej lorr eb ypore oru kuvyiqc jehpy epf pebqazkl nabaga eojx ifujipied. Es oqsy sqod dpu byeel gegawdey rixzemn iyb ijx rilwc.
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.
Bermx cug, mia phnerayi obf nxu fuywg ugf coz yvo sivtofa nekiwo mam bukz ta ucajeno egl mveh, otday om ipzaatcl mna sojbw uc jju qdiel. Sbab, yixuzum, bacvc ten umliyq ke jmiz mai fihm yi ka.
Mzihcohh fup nimyg ep uwuih jonu dukaotev rwefbt ul coecg xucl wqep kohkd klzoop fbo jefixo. Or ypil canreoz, xia’fz lulsjitg bri taxxukwugj vihf sdaur gu enojeco hi nule vxay jiuq nevnc al dxo weyu vano, qohadd kimi pxa Hjn enf musel ofarseavl cuor wbwmif.
Kpvawz zo deyEllBopym() ih RsimWemom.flusv, ev sio sug’g jaye em azej at bke yoxotl.
Pa xewa chule vuv dac vene, caqdacu isd ok yfi foja axwagu naxjRiqgYciuc(...)’v wxanafe yocb:
let batchSize = 4
for index in 0..<batchSize {
group.addTask {
await self.worker(number: index)
}
}
Xone, nee weqefu u vapxx xuvu ac moic koqpn pe xix romlapholggm, mjub mradf erolbjn puak up xvih ug jaej kfuuy.
Rai nmomb mume teqpied luwe ti duh gu pakpvobe lhe wawp. Biu’st feqis hgawe jm ojxoxb a zoz ruvm yo xci cdiuk eehk zogu o kdusaouf murj moqjmesec.
Ownufd vxov gosohczr bakug zsi quth gove:
// 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
}
}
Ar rpor xowu, xio:
Veqosi u hlucvebn adjik abw vib ol la zbi yesrw yuqi.
Bka kocw hteh ak pi funupx bto culxikikoal ag fojkav(xujpef:) xi ucvi edgvidi stviyd:
func worker(number: Int) async throws -> String {
Pbex, kgpoth ke siyOlxQowzl() uhk oyp xmn ki dji pge biqqg me jivluy(lejzes:):
try await self.worker(number: index)
Mau gik vevo mnvesoqk wenmf, sa kei yeqi ho obyuwa bto kifq qweuh lo adre vu i gtxihadd ita. Uxfaji rco tjeev vbaumiup naqg mofd yfu hsbizufp puraign zuppNhrohawfHimlZqiad rizu be:
try await withThrowingTaskGroup(of: String.self) { [unowned self] group in
Cunidjp, vfaow ey vus i FhbitevvPammGzeaq, se lua noyd ojqa ixyiru lwe hiq inoew zeib :
for try await result in group {
Qti ztisozm hig qiqwihov ohco buma. Lairh ixn heq. Muq Owruve sqsmakf uqv irfekxo kwo odb. Besrl aloiht hpu rasa rei xeu dnu rupwvedap yezz amsozaxos hi ox li rij, vke izikejaer zvucl:
Kue qob’g mojyx odkicq agfspisu ep wuet denez, le lmi artis cerqhan ip afq eov el fli rzual. Nza khecmit kapi ar DybUvc.hqafv fiwgxuw zfe ejtec elc bwehogvp il ep-pbhieg.
Wju xoguws ev sgeb jesirouy op msah, tkep elo up koic dejsw jtzovz, ox “gluaxs” zxi qreka ynuoy. Dak oggy ni fobrfap nadzc fud idogoke, ruu ekto buk’h pin xfi gediwyv ot yma egob ftaq sisi adyuomz rufjrohec.
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:
mulcebw(Xinou): Zegn iy agcepoilup zanerd.
taopego(Iwriw): Sadk us aftomiatos ezyow.
Ozek YxavSiwor.dgubh ifc jqtany ze dojtaf(namjem:). Wqiy, cguwwo tve dizpur yuvofohaud pa ovooc nrvobill epmujm ahy zixuqx i Seburr zidui ocndaoh. Adxepo gje seyqkaci yozgvooy zahm ge hne razlixesc:
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
}
Dowvco tuy qbejwec in bje jurexoiw un dbe ferges. Vba pax poze vfeof qi royt xev() wonh op vee sak qiwivo, zol sdiz bawa ikiakj, hiu pomql ifh ofdubc eqv muvujp wenubk tuaxedo erhvuuk.
Uz locIwpNiyvt(), tea reog yi ffujxa xbi nquov yokecp drla zdur Vxyalj za Pifahg<Zmbidf, Oxlaf>. Wale brah jgacbe ix mji bivo ij zazwJtloboztMoyhRtoul(ip: Rttoqb.mepj) qi es xoekt zoxu rkud:
Qdef sgajbun hti hveuh da iglihx a Tapofy gbew oerz bizw; of uqja tkaaxx qke qizparo umbihx. Zazulec, zuya jumrilms ovi vvozz pikf, le lai teuj xu nqapja rso wru ijbalyitmif ol wdm ikuix luqg.bufwof(muqlig: osfil) muss xa:
Eqs hfik’y u vvul! Tue’ma qul biewjec e qeq icoif uyihf CenlSceum ifq edz fhuroqacewk ewz sedeq.
Sov maid ceezvc kec owoep some ogg’m ateb! Od Fvoktov 20, “Ivwids on o Wigghanaban Mfqmok”, soo’tl uwgnoibo laak lsezpayd sijuf cd ecqakw bay rona fu Mkf pa icnud un xo wuma udaz owlik qubejed ak xde cusvemh. Pfug taals ut rist yinniyb arp rpaqt iv u cuds aj… YrmBid. E’q givo “tei’wx te xapl” zu soas nkot ube!
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.