Net-Base Tímarit

24.07.2026

Delphi: TParallel.For með þráðöruggu Progress-UI (TThread.Queue) án deadlocka

Svona sameinar þú Delphi TParallel.For við þræðisöruggt framvinduviðmót: uppfærslur í gegnum TThread.Queue, hreint samantekt, stjórnun stöðvunar og algengar deadlock-fellur við kembun og rekstur.

24.07.2026

Frá tímaritsþema til verkefnaframkvæmdar

Viðeigandi þjónustu- og tæknisíður fyrir greinina

Sá sem í Delphi vill keyra reiknintensív eða I/O‑þung verkefni samhliða lendir fljótt hjá Parallel Programming Library (PPL) og nánar tiltekið hjá TParallel.For. Ávinningurinn sést oft strax – þar til á sama tíma er reynt að uppfæra Progress-UI „bara svona“. Einmitt þar koma fram hin dæmigerðu hik: tilviljunarkenndar frystingar í UI, ProgressBar sem hoppar afturábak, eða fullkominn deadlock þegar farið er í gegnum kóðann með debugger skref fyrir skref.

Í þessari grein er fjallað um TParallel.For threadsichere Progress-UI: traust mynstur sem virkar í VCL og FMX, sem bundlar UI‑uppfærslur snyrtilega með TThread.Queue, tekur tillit til Cancel/Abort og forðast afdráttarlaust algengustu deadlock‑gildrur. Áherslan er á raunverulega rekstur: endurgeranlegt atferli, skýr ábyrgðarsvið og villuleitaráð sem gagnast jafnvel þegar villan kemur einungis „hjá viðskiptavinum“.

Af hverju Progress-UI fer svo oft úrskeiðis með TParallel.For

TParallel.For keyrir yfirleitt á vinnuþráðum úr Delphi-þráða‑bassin. Þessir þræðir mega ekki snerta VCL‑ eða FMX‑controls beint, því UI‑rammaskipunin (Message Loop, Window Handles, Rendering) er tengd aðalþræðinum. Jafnvel tilvísun sem virðist hættulítil, ProgressBar.Position := …, frá vinnuþræði getur valdið óútskýrðu atferli: tilviljunarkenndum AV‑villum, frosnum gluggum eða „flakkandi“ uppfærslum.

Hin augljósa viðgerð er oft TThread.Synchronize. Hún lagar vissulega þráðaöryggi, en leiðir fljótt til annars vandamáls í samhliða lykkjum: hún býr til raðbundna þrengsli. Hver vinnuþráður bíður eftir aðalþræðinum, sem aftur er upptekinn með renderun og meðhöndlun Synchronize‑kölla. Við álag lítur þetta út eins og deadlock – jafnvel þó það sé „bara“ starvation/lockstep‑áhrif.

Og svo er til hin raunverulega deadlock‑tegundin: Aðalþráðurinn bíður (t.d. með WaitFor, Task.Wait eða óbeint í gegnum blokkandi köll) eftir að samhliða‑aðgerðin ljúki, á meðan vinnuþræðir reyna að senda vinnu inn í aðalþráðinn með Synchronize eða vegna óhentugrar Queue-notkunar. Niðurstaða: aðalþráðurinn bíður eftir vinnuþræðinum, vinnuþræðirnir bíða eftir aðalþræðinum.

TThread.Queue vs. TThread.Synchronize: der praktische Unterschied

Unschärfer Debugger-Blick mit Notizen zu Threads und Queue als Kontext für Queue vs Synchronize
Við villuleit sér maður fljótt hvort vinnuþræðir bíði eftir aðalþræði eða bara setji uppfærslur í biðröð (queued).

Báðir hlutirnir hafa það sameiginlegt að keyra kóða örugglega í aðalþræði. Munurinn liggur í bíðsemanatík:

  • TThread.Synchronize: Sá vinnuþráður sem kallar á bíður þar til aðalþráðurinn hefur keyrt kóðann. Þetta er „samtímt“, eykur töf og er klassísk orsök deadlocka þegar aðalþráðurinn er læstur.
  • TThread.Queue: Worker-þráðurinn setur kóðann aðeins í biðröð fyrir aðalþráðinn og heldur áfram að keyra. Þetta er „asynkrón“, losar um tengingu þráða og er í samsíða-senurómum næstum alltaf betri sjálfgefna lausn – fyrir utan þegar þarf að stjórna uppfærslu­tíðni.
  • Mikilvægt: Queue er ekki frímiði. Ef þú sendir Queue-uppfærslu í hverri ítrun lykkju flýturðu biðröð aðalþráðsins. Þá hengir ekki UI vegna deadlocks, heldur vegna gríðarlegs fjölda skilaboða. UI virkar „seigt“ og vinnslu lokið tefst, því hundruðir eða þúsundir UI-uppfærslna eru enn í röðinni.

    Der Randfall, der wirklich wehtut: Warten im UI-Thread

    Í fyrirtækjaforritum sést oft eftirfarandi ferill: smellt er á hnappinn „Start“, UI er gerð óvirk, ProgressDialog opnast og svo er synchron „bídduð“ þar til allt er tilbúið og síðan virkjað aftur. Þessi mynstr er kjarni margra deadlocks.

    Algengar útgáfur (eftir kóðabasis):

    • Aðalþráðurinn ræsir TParallel.For og kallar síðan á blokkerandi biðlógík (beint eða óbeint).
    • ProgressDialog kallar í Constructor eða OnShow á rútínu sem bíður innanhúss.
    • Hætta-hnappur setur vissulega flagg, en aðalþráðurinn situr samt fastur í biðlykkju.

    Ef worker-þræðir nota á þessum tíma Synchronize, er deadlock nánast öruggur. Með Queue getur það líka hangið ef aðalþráðurinn er blokkerandi og pumbað er ekki út skilaboðum – því þá er ekki unnið úr biðröðinni.

    Reyndar rekstrarleggur afleiðingarnar eru: Aðalþráðurinn má ekki bíða blokkerandi eftir parallel-lykkjunni þegar samsíða UI-uppfærslur eru nauðsynlegar. Í staðinn þarf vinnslan annað hvort að vera flutt algjörlega í bakgrunnsverkefni, eða skipulögð með „asynkrónu endi“ (Callback/Queued Abschlussaktion) sem sleppir UI aftur lausum við lok.

    Sauberer Ansatz: Progress nur aggregiert, UI-Updates gedrosselt

    Vinnustaðsmynd með abstraktaða framvindusýningu og tímamælingu sem tákn fyrir takmarkaðar UI-uppfærslur
    Söfnun og tímabundin drepun hindra að UI verði flóðað af of mörgum uppfærslum.

    Traust mynstur skiptist í þrjár skýrt aðgreindar ábyrgðir:

    • Worker-Threads framkvæma sjálfa vinnuna fyrir hvert atriði/indx. Þeir senda aðeins framvindu í þráðöruggri mynd (teljari, Queue, þráðörugg biðröð).
    • Aggregator (oft: aðalþráðurinn eða sértækur tími í UI) reiknar út úr framvindunni stöðu fyrir UI (staða, texti, ETA) og uppfærir stýrikerfi. Þannig forðastu 1:1-uppfærslur fyrir hverja ítrun.
    • Lokaaðgerð (einnig í aðalþræði): virkja UI aftur, sýna niðurstöðu, taka saman villur og losa auðlindir.

    Af hverju þessi aðskilnaður virkar svona vel: Vinnuálag getur verið háþrýst (þúsundir atriða), en UI þarf aðeins fáar uppfærslur á sekúndu. Í framkvæmd duga 5–10 uppfærslur/sekúndu, hjá mjög hraðum jobbum jafnvel 2–4. Allt þar yfir er oftast bara sjónræn truflun og kostar CPU-tíma í skilaboðapumpunni.

    Þráðörugg telning: atomískt frekar en læsing

    Fyrir einfaldan ProgressBar dugar oft atómtækur teljari. „Atomískt“ þýðir að innhækkanir og lestrar fara fram án race condition, yfirleitt með TInterlocked. Með þessu forðast þú læsingar (Critical Sections) í heita kóðanum í lykkjunni.

    Sannað grunnmynstur:

    • Heildarmagn er þekkt fyrirfram (t.d. fjöldi færslna, skrár, IDs).
    • Í hverri ítrun eykst DoneCounter atómtískt.
    • UI-tími les teljarann reglulega og stillir ProgressBar.Position.

    Kostur: Ekkert TThread.Queue fyrir hvert atriði, engin ofhlaða á UI. Gallinn: Þú hefur ekki nákvæmar stöðumeldingar fyrir hvert atriði (t.d. skráarnafn). Þess í stað má bæta við annarri, takmarkaðri stöðumeldingunni (sjá næsta kafla).

    Stöðumeldingar án spam: „síðasta staða ræður“

    Ef þú vilt, auk þess, sýna stuttan texta (núverandi atriði, stig, villuskilaboð), þarf einnig mynstur sem flæðir ekki UI-ið við hvert vinnuskref. Í framkvæmd virkar „síðasta staða ræður“ mjög vel:

    • Worker skrifar stöðuupplýsingar í þráðörugga gagnauppbyggingu (t.d. atómtækt skiptanlegan streng eða varið með litlum læsingum).
    • UI-tími afritar reglulega síðustu stöðu inn í Label.

    Þannig helst UI viðbragðsfljótlegt, og þú sérð samt að „eitthvað er að gerast“. Mikilvægara hér er ekki strengurinn sjálfur heldur líftíminn: ekki bera tilvísanir í skammlífar hluti úr Worker-þráðum yfir í UI-þráðinn. Ef þú sendir hluti, tilgreindu eignarhald (ownership) skýrt.

    TParallel.For þráðörugg Progress-UI með TThread.Queue: traust mynstur

    Það eru senaríur þar sem UI-tími einn og sér dugar ekki: t.d. þegar þú vilt tryggja nákvæmlega eina „Lokið“-uppfærslu í lokin, eða þegar UI-uppfærslan er flóknari aðgerð (t.d. færsla í logg-glugga, en takmörkuð). Þá hentar TThread.Queue – en ekki fyrir hverja ítrun, heldur markvisst.

    Praktísk nálgun er röð eingöngu fyrir atburði með lága tíðni:

    • Start-atburður (undirbúa UI, læsa hnöppum)
    • Tímabundnir framvindu-atburðir (hámark einu sinni á X millisekúndur)
    • Villa-atburðir (valfrjálst safnaðir)
    • Lokaatburður (endurstilla UI, sýna niðurstöðu)

    Þessa tímamynd náið ekki í UI-þræðinum heldur í Worker-samhengi: leyfðu Worker að queuea UI-uppfærslu aðeins ef nægur tími er liðinn frá síðustu UI-uppfærslu. Til þess hentar monotón tímatilfelli (t.d. TickCount) ásamt atómtísku „last update“-gildi.

    Mikilvægt: UI-uppfærslan sjálf verður að vera „hröð“. Dýr útreikningavinna, skráar-I/O eða gagnagrunnsaðgerðir eiga ekki heima í gequeuðu UI-callback-i. Callback-ið ætti aðeins að lesa út stöður og setja Controls.

    Stöðvun: Afbryta án þess að forritið frjósi

    Í raunverulegum forritum er afbryting ekki valkvæð. Meginatriðið er: afbryting er ekki „Kill“, heldur samvinnulegt lok. Worker-þræðir verða reglulega að athuga hvort afbrotsmerki (Abbruchsignal) sé sett og þá yfirgefa hreint. Í Delphi eru nokkrar leiðir til þess (eftir PPL-uppbyggingu): eigið Volatile-flag, atómtískur Boolean, eða Cancellation-hugmynd byggð á Tasks (eftir Delphi-útgáfu og uppbyggingu).

    Fyrir rekstur eru tvær reglur mikilvægar:

    • Cancel þarf að verða sýnilegt fljótt: Athugið stöðvunarfánann á viðeigandi stöðum, ekki aðeins í lok ítrunar þegar ítrunin getur tekið nokkrar sekúndur.
    • Cancel þarf að ryðja upp: Opuð handföng, tímabundnar skrár, gagnaviðskipti eða læsingar mega ekki liggja eftir. Þetta þýðir: Í hverri Worker-ítrun eru try/finally-kubbar nauðsynlegir þegar auðlindir koma við sögu.

    Í notendaviðmótinu ætti Cancel aðeins að setja merki og færa viðmótið í „Stopping…“-ástand. Sjálf lokun og endurvirkjun viðmótsins eiga að gerast í Done-Event, ekki strax við smell.

    Forðast deadlocks: algengustu gildrurnar í framkvæmd

    Diagramm eines zyklischen Wartens zwischen Threads als Visualisierung eines Deadlocks
    Deadlocks myndast oft vegna hringlaga biða: UI-þráðurinn er hindraður, worker-arnir bíða eftir aðgangi að UI.

    Gildra 1: WaitFor/Task.Wait í Main Thread

    Ef aðalþráðurinn er hindraður getur hann ekki framkvæmt Queue-callbacks né unnið úr skilaboðum. Þetta líktist deadlock, jafnvel þótt worker-arnir haldi áfram eðlilega. Lausn: Engar blokkerandi bið-aðgerðir í UI-þræðinum. Í staðinn framkvæmið lokaaðgerð yfir TThread.Queue eða notið atburðarstjórnunar (t.d. tímasetjari sem athugar „fertig“).

    Gildra 2: Synchronize innan læsingar

    Klassískt dæmi: Worker heldur á Critical Section, kallar svo á Synchronize, og í UI-callbackinu þarf (beint eða óbeint) aftur sama Critical Section. Niðurstaða: hringlaga bið. Reglan er einföld: Ekki afhenda til UI (Synchronize/Queue) meðan læsing er haldin. Ef læsing er nauðsynleg, sækjið öll gögn í staðbundnar breytur, sleppið læsingunni og setjið svo í Queue.

    Gildra 3: UI-Callback veldur endurkomu (Reentrancy)

    Stundum er UI-uppfærsla sjálf ekki „saklaus“: að stilla Properties getur kallað fram atburði (OnChange, OnResize) sem aftur ræsa röksemdaferla sem nálgast Worker-ástand. Þetta er ekki deadlock í þröngum skilningi, en veldur illa skýran föstum stöðum og race conditions. Leiðrétting: setjið UI-uppfærslur í „þögla“ ferla (slökkvið tímabundið á atburðum) eða notið reentrancy-guards (t.d. atómískur gæsla fyrir uppfærsluáfanga).

    Gildra 4: Of margar queued uppfærslur

    Jafnvel án Waits getur viðmótið stöðvast ef þið bíðið upp tugþúsundir queued callbacks. Einkenni: ProgressBar heldur áfram að renna í langan tíma, gluggi svarar hægt, CPU-notkun í aðalþræði hærri. Lausn: Dregið úr tíðni (tímagluggi), safna saman (teljari), eða nota raunverulega producer/consumer-uppbyggingu þar sem aðeins ein UI-uppfærsla getur verið „pending“ (coalescing).

    Þegar það verður flóknara: Safna niðurstöðum, sameina villur, tryggja röðun

    TParallel.For hentar vel þegar ítrun eru sjálfstæðar. Í fyrirtækjaforriti eru ítrun þó oft aðeins „að miklu leyti“ sjálfstæðar: þær lesa skrár, kalla á REST-APIs, skrifa gagnagrunnslínur. Þá þarf að skipuleggja þrjú viðbótaratriði gaumgæfilega:

    • Þráða-örugg söfnun niðurstaða: Annað hvort staðbundið buffer fyrir hvern þræði (sameina í lokin) eða þráðaörugg Queue/Collection. Forðast læsingar á hot path.
    • Villumeðhöndlun: Undantekningar úr vinnuþræðunum þarf að safna. Í framkvæmd reynist best: muna fyrstu undantekninguna og kalla fram hættun, eða safna öllum undantekningum og birta þær samanlagt í lokin.
    • Röð: Ef úttakið þarf stöðuga röð (t.d. logg eftir vísitölu), er samhliða vinnsla með síðarri röðun oft einfaldari en að reyna að gera „þráðörugga raðaða innsetningu“.

    Fyrir UI þýðir þetta: sýnið ekki hvert einasta villuboð strax. Þetta leiðir til yfirgnæfandi fjölda modalglugga. Safnið villum (t.d. lista af strengjum) og sýnið í lokin samantekt eða útfluttan loggskrá.

    Rökgreining: Hvernig gera má deadlock raunverulega sýnilegan

    Deadlocks í samhliða kóða eru pirrandi, því þau geta litið öðruvísi út í debugger en í release-útgáfu. Þó eru nokkrir mjög gagnlegir þræðir (levers):

    Nota þræðaglugga og kallstakkana

    Ef viðmótið frýs, skoðið alla þræði: Hvar stendur aðalþráðurinn? Bíður hann? Er hann í skilaboðahring? Hvar standa vinnuþræðirnir? Ef vinnuþræðir hanga í Synchronize, er orsökin nánast alltaf að aðalþráðurinn sé stíflður eða að aðalþráðurinn þurfi læsingu.

    Merkja biðröð-/Synchronize-staði

    Setjið markvisst logg við yfirfærslustaði (fyrir biðröðina, í biðröðarkalli/Queue-Callback, í lok ítrunar). Í rekstri er þetta oft verðmætara en breakpoints, því tímasetning skiptir máli. Gætið þess að logg sjálft sé þráðöruggt og ekki hindrandi (t.d. engin beint UI-logg úr vinnuþræðum).

    Mæla síðustu uppfærslutíma

    Ef UI „frýs“ getur einfaldlega verið að það þurfi að vinna úr of mörgum uppfærslum. Mælið því í aðalþráðnum hve oft á sekúndu þið framkvæmið UI-uppfærslu og hversu langan tíma hún tekur. Þegar UI-kallböck taka fleiri en nokkrar millisekúndur þarf að hægja á eða einfalda uppfærslurnar.

    Hvenær borgar sig TParallel.For með Progress-UI í raun?

    Samhliðavinna er ekki endamarkmið. Hún borgar sig sérstaklega þegar:

    • endurtökur séu nægilega stórar (millisekúndur til sekúndna), þannig að yfirbygging þræðahópsins (threadpool-overhead) verði óveruleg,
    • verkið sé CPU-þungt (Parsing, Kompression, Hashing) eða hafi vel samhliða I/O (margar skrár, mörg HTTP-requests með takmörkunum),
    • þið hafið skýra hættustefnu (Cancel) og villustefnu,
    • UI-kröfur leyfa samansafnaða framvindu (aggregated progress).

    Það borgar sig síður þegar hver ítrun er afar stutt (ör-aðgerðir) eða þegar allar ítrunir rekast á sama þröskuldinn (raðbundin DB-viðskipti, global læsing, ein skrá). Þá er oft skilvirkari nálgun að bæta reiknirit, vinna í hnippum (batching), draga úr gagnaaðgangi eða aftengja þröskuldinn sérstaklega.

    Hagnýt athugunarskrá: Svo helst viðmótið stöðugt

    • Aðalþráðurinn má ekki stíflast: engar biðpantanir, engar langar lykkjur án skilaboðahrings.
    • Vinnuþræðir snerta aldrei Controls: engin VCL/FMX-aðkoma utan UI-þráðs.
    • UI-uppfærslur eru þjappaðar: teljarar/tímarar eða sameinaðar biðröðuuppfærslur í stað per-ítrunar.
    • Engin Synchronize-kall úr læsingum.
    • Cancel er samvinnubundin, reglulega athuguð og sinnir hreinsun á réttan hátt.
    • Undantekningar eru safnaðar og meðhöndlaðar skipulega í lokin.

    Niðurstaða: Aftengja með biðröð, styrkja með samansöfnun

    Traustur Progress-UI við TParallel.For skapast ekki með því að „setja Synchronize einhverstaðar inn“, heldur með skýru arkitektúrprincipi: Worker vinna sjálfstætt, aðalþráðurinn (Main Thread) helst laus og meðhöndlar aðeins fá og hraðvirk UI-uppfærslur. TThread.Queue er rétt verkfæri til þess þegar þú notar það markvisst og takmarkað. Fyrir „seigt“ eða óstöðugt hegðun eru nánast alltaf tvær orsakaþættir ábyrgir: aðalþráðurinn bíður einhvers staðar í læsandi aðgerð – eða hann drukknar í of mörgum uppfærslum sem settar eru í biðröð.

    Þegar þú hefur einu sinni sett mynsturinn rétt upp (Counter/Coalescing, Cancel-Flag, Abschluss-Callback), borgar það sig yfir mörg svið í eldri Delphi-forriti: Import/Export, gagnaskoðanir, skráa- og API-verk – allt verður viðbragðsmeira án þess að þú mishafi nýja deadlocks við hverja Progress-uppfærslu.

    Ef þú þarft aðstoð við að gera parallel-kóða stöðugan, við bilanaleit á UI-föstum ástandi eða við hreina endurnýjun eldri Delphi-forrita: Hafðu samband.

    Fyrir þetta efni eru einnig Delphi Parallel Programming Library og Tthread.queue Vs Synchronize mikilvæg. Greinin setur þessa þætti skýrt í samhengi og sýnir hvað skiptir mestu máli í daglegri vinnu.

    Ræddu verkefni eða endurnýjunaráform með Net-Base.

    Næsta skref

    Wenn aus dem Thema ein reales Projekt wird, sollten Architektur, Bestand und Betrieb früh zusammen betrachtet werden.

    Við styðjum ekki aðeins við einstakar spurningar, heldur einnig þegar úr kóðabútum, eldri kerfum eða gáttahugmyndum þarf að verða traust fyrirtækjaverkefni.

    • Núverandi staða, markmynd og tæknileg áhætta eru metin saman.
    • REST, Datenzugriff, Portale und Rollout werden nicht als Spätfolgen verschoben.
    • Þú sérð snemma hvaða leið er efnahagslega og rekstrarlega framkvæmanleg.

    Deila færslu

    Deila þessari færslu beint

    LinkedIn, X, XING, Facebook, WhatsApp und E-Mail sind sofort verfügbar. für Instagram bereiten wir Link und Kurztext direkt vor.

    Tölvupóstur

    Instagram opnast í nýjum flipa. Tengill og stuttur texti eru afritaðir í klippiborðið á undan.