Net-Base Magasin

24.07.2026

Delphi: TParallel.For med trådsikker framdrifts-UI (TThread.Queue) utan dødlåsar

Slik kombinerer du Delphi TParallel.For med ein trådtrygg Progress-UI: oppdateringar via TThread.Queue, ryddig aggregering, handtering av avbrot og vanlege deadlock-fallar ved feilsøking og drift.

24.07.2026

Frå magasinetema til prosjektpraksis

Passande teneste- og tekniske sider til innlegget

Den som i Delphi vil parallelisere reknintensive eller I/O-tunge jobbar, kjem fort bort i Parallel Programming Library (PPL) og konkret TParallel.For. Effekten er ofte målbar straks – inntil ein i same andedrag vil oppdatere ein Progress‑UI «sånn litt kjapt». Nøyaktig her oppstår dei typiske hengane: tilsynelatande tilfeldige UI‑freezes, ein ProgressBar som hoppar bakover, eller ein komplett deadlock så snart ein køyrer steg‑for‑steg i debuggar.

I dette innlegget handlar det om TParallel.For threadsikre Progress-UI: eit robust mønster som fungerer i VCL og FMX, som samlar UI‑oppdateringar over TThread.Queue på ein rein måte, tek omsyn til Cancel/Abort og som konsekvent unngår dei vanlegaste deadlock‑fellene. Fokuset ligg på driftsrealitet: reproducerbart åtferd, klare ansvarsgrenser og debugging‑tips som også hjelper når feilen berre opptrer «hos kundar».

Kvifor Progress‑UI går så ofte gale med TParallel.For

TParallel.For køyrer typisk på worker‑trådar frå Delphi‑threadpoolen. Desse trådane må ikkje røre VCL‑ eller FMX‑controls direkte, fordi UI‑rammeverka (Message Loop, Window Handles, Rendering) er bundne til hovedtråden. Allereie ein tilsynelatande heilt ufarlig ProgressBar.Position := … frå ein worker kan gi udefinert åtferd: sporadiske AVs, innfrosne vindauge eller «flakkande» oppdateringar.

Den naturlege reparasjonen er ofte TThread.Synchronize. Det ordnar visst nok trådtryggleik, men fører i parallelle løkker raskt til eit anna problem: det skaper ein seriell flaskehals. Kvar worker ventar på hovedtråden, som på si side er opptatt med rendering og med å handtere Synchronize‑kall. Under last ser dette ut som ein deadlock – sjølv om det «berre» er ein starvation/lockstep‑effekt.

Og så finst den verkelege deadlock‑klassa: Hovedtråden ventar (t.d. via WaitFor, Task.Wait eller indirekte gjennom blokkerande kall) på at parallel‑operasjonen skal fullføre, samtidig som worker‑trådane prøver å sende arbeid til hovedtråden med Synchronize eller ved uheldig bruk av Queue. Resultat: hovedtråden ventar på worker, worker ventar på hovedtråden.

TThread.Queue vs. TThread.Synchronize: den praktiske skilnaden

Uskarpt debugger‑blikk med notat om trådar og Queue som kontekst for Queue vs Synchronize
Ved debugging ser ein raskt om worker‑trådar ventar på hovedtråden eller berre køar oppdateringar.

Begge mekanismar finst for å køyre kode trygt i hovedtråden. Forskjellen ligg i vente‑semantikken:

  • TThread.Synchronize: Den kallande workeren ventar til hovedtråden har køyrt koden. Det er «synchron», aukar latenstider og er ein klassisk ingrediens i deadlock‑situasjonar når hovedtråden er blokkert.
  • TThread.Queue: Arbeidaren plasserer koden berre i ei kø for hovudtråden og held fram med å køyre. Det er „asynkront“, koplar frå trådar og er i parallell-scenario nesten alltid det betre standardvalet – så lenge ein kontrollerer oppdateringsfrekvensen.

Viktig: Queue er ingen fribillett. Dersom du i kvar iterasjon av ei løkke sender ein Queue-oppdatering, fløymer du hovudtråd-køen. Då heng UI-en ikkje på grunn av deadlock, men på grunn av rein mengd meldingar. UI-en verkar „treig“, og sluttføringa av behandlinga blir forseinka fordi hundrevis eller tusenvis av UI-oppdateringar framleis skal køyrast.

Kanttilfellet som verkeleg gjer vondt: å vente i UI-tråden

I bedriftsapplikasjonar ser ein ofte denne flyten: knapp „Start“ blir klikka, UI blir deaktivert, ProgressDialog blir vist, så blir det synkront „venta“ til alt er ferdig, for så å aktivere att. Dette mønsteret er kjernen i mange deadlocks.

Typiske variantar (avhengig av kodebasen):

  • Hovudtråden startar TParallel.For og kallar deretter ei blokkjerande ventelogikk opp (direkte eller indirekte).
  • Ein ProgressDialog kallar i Constructor eller OnShow ein rutine som ventar internt.
  • Ein avbryt-knapp set visst eit flagg, men hovudtråden sit likevel i ein venteloop.

Dersom worker-threads i denne perioden brukar Synchronize, er deadlock praktisk tala garantert. Med Queue kan det òg fryse om hovudtråden er blokkert og ikkje pumpa meldingar – for då blir heller ikkje Queue handsama.

Den driftssikre konsekvensen er: Hovudtråden må ikkje blokkere og vente på parallell-løkkja dersom det trengst parallelle UI-oppdateringar. I staden må behandlinga anten flyttast fullt ut til ein bakgrunnsoppgåve, eller ein organiserer ei „asynkron avslutning“ (Callback/Queued avslutningshandling) som frigjer UI-en til slutt.

Ryddig tilnærming: Progress berre aggregert, UI-oppdateringar drossla

Arbeidsplassmotiv med abstrahert Progress-vising og tidtaking som symbol for drossla UI-oppdateringar
Aggregering og tidsbasert drossling hindrar at UI-en blir oversvømt av for mange oppdateringar.

Eit robust mønster består av tre klart adskilde ansvarsområde:

  • Worker-Threads utfører det faktiske arbeidet per element/index. Dei rapporterer berre framgang i ei trådsikker form (tellar, Queue, Thread-safe Queue).
  • Aggregator (ofte: hovudtråden eller ein dedikert timer i UI) reknar ut frå framgangen ein UI-status (posisjon, tekst, ETA) og oppdaterer kontrollar. Slik unngår ein 1:1-oppdateringar per iterasjon.
  • Avslutning (også i hovudtråden): reaktivere UI, vise resultat, oppsummere feil, frigjere ressursar.

Kvifor denne delinga fungerer så godt: Arbeidsmengda kan vere høgfrekvent (tusenvis av element), men UI treng berre nokre få oppdateringar per sekund. I praksis held 5–10 oppdateringar/s, ved svært raske jobbar til og med 2–4. Alt over det er som regel berre optisk støy og kostar CPU-tid i meldingsløypa.

Trådtrygg teljing: atomisk i staden for lås

For ein enkel ProgressBar held ofte ein atomisk teljar. «Atomisk» betyr: inkrement og lesing skjer utan race condition, typisk via TInterlocked. Det lèt deg unngå låsingar (Critical Sections) i den kritiske stien til løkka.

Velprøvd minimalidé:

  • Totalmengda er kjend på førehand (t.d. talet på postar, filer, ID-ar).
  • Kvar iterasjon aukar ein DoneCounter atomisk.
  • Ein UI-timer les teljaren periodisk og set ProgressBar.Position.

Fordel: Ingen TThread.Queue per element, ingen UI-overflod. Ulempe: du har inga detaljmelding per element (t.d. filnamn). Til dette kan ein leggje til ei sekundær, neddrosa statusmelding (sjå neste avsnitt).

Statusmeldingar utan spam: «siste status vinn»

Om du i tillegg vil vise ein kort tekst (aktuelt element, fase, feilmelding), trengst det òg eit mønster som ikkje flommer UI-en ved kvart worker-steg. I praksis fungerer «siste status vinn» veldig godt:

  • Worker skriv ei statusinformasjon til ei trådtrygg struktur (t.d. ein atomisk utskiftbar streng, eller verna med ein liten lås).
  • UI-timer overtek periodisk den sist observerte statusen i eit Label.

Slik held UI-en seg responsiv, og du ser likevel at «det skjer noko». Viktigare her er ikkje strenginnhaldet i seg sjølv, men levetida: ikkje slep referansar til kortlivde objekt frå worker-trådar inn i UI-tråden. Om du sender objekt, avklar eigarskap eksplisitt.

TParallel.For trådtrygg Progress-UI med TThread.Queue: eit robust mønster

Det finst scenarier der ein UI-timer aleine ikkje strekk til: t.d. når du på slutten vil utløyse nøyaktig éin «Fertig»-oppdatering, eller når UI-oppdateringa er eit meir komplekst steg (t.d. ein oppføring i eit loggfelt, men drosla). Då er TThread.Queue høveleg – men ikkje per iterasjon, snarare målretta.

Ein praktisk tilnærming er ei kø berre for hendingar med låg frekvens:

  • Start-hending (gjer UI klar, sperr knappar)
  • Periodiske progress-hendingar (maks kvar X Millisekunden)
  • Feil-hendingar (valfritt samla)
  • Ferdig-hending (tilbakestill UI, vis resultatet)

Periodisiteten oppnår du ikkje i UI-tråden, men allereie i worker-kontexten: du lèt workerane berre då queuee eit UI-oppdateringsevent når det har gått tilstrekkeleg tid sidan siste UI-oppdatering. Til dette passar ei monotont tidskjelde (t.d. TickCount) pluss ein atomisk «last update»-verdi.

Viktig: Sjølve UI-oppdateringa må vere «rask». Dyre berekningar, fil-I/O eller databaseaksessar høyrer ikkje heime i den gequeua UI-callbacken. Callbacken bør berre lese ut tilstandar og setje Controls.

Cancel-handtering: Avbryt utan at det heng seg

I reelle applikasjonar er avbryting ikkje valfritt. Avgjerande er: Cancel er ikkje ein «Kill», men ei kooperativ avslutting. Worker må jamleg sjekke om eit avbrotssignal er sett, og så avslutte ryddig. I Delphi finst det fleire vegar for dette (avhengig av PPL-konstruksjonen): eit eige Volatile-flag, ein atomisk Boolean, eller eit Cancellation-konsept over Tasks (avhengig av Delphi-versjon og -struktur).

For drift er to reglar viktige:

  • Avbryt må bli synleg raskt: Sjekk avbrotsflagget på fornuftige punkt, ikkje berre ved slutten av ei iterasjon når iterasjonen kan ta fleire sekund.
  • Avbryt må rydde opp: Opne handles, midlertidige filer, transaksjonar eller lås må ikkje liggje att. Det vil seie: I kvar Worker-iterasjon er try/finally-blokker obligatoriske når ressursar er involverte.

På UI-sida bør Avbryt berre setje eit signal og setje UI i ein „Stopping…“-tilstand. Den faktiske avslutninga og reaktiveringa av UI skjer så i Done-Eventet, ikkje med ein gong ved klikk.

Unngå deadlocks: die häufigsten Fallen in der Praxis

Diagramm eines zyklischen Wartens zwischen Threads als Visualisierung eines Deadlocks
Deadlocks oppstår ofte ved syklisk venting: UI-tråd blokkert, Worker ventar på UI-tilgang.

Falle 1: WaitFor/Task.Wait im Main Thread

Når Main Thread blir blokkert, kan han ikkje køyre Queue-callbacks eller prosessere meldingar. Det verkar som eit deadlock, sjølv om Worker-ane held fram korrekt. Løysing: Unngå blokkerande venting i UI-tråden. I staden utfør avslutningshandling via TThread.Queue eller ei hendingstyring (t.d. ein timer som sjekkar „ferdig“).

Falle 2: Synchronize innerhalb eines Locks

Eit klassisk døme: Ein Worker held ei Critical Section, kallar deretter Synchronize, og i UI-callbacken blir den same Critical Section (direkte eller indirekte) påkravd att. Resultat: sirkulær venting. Reglane er enkle: Ingen UI-overføring (Synchronize/Queue) frå innanfor eit halde lock. Om eit lock er naudsynt, kopier alle data til lokale variablar, frigjer locken og set deretter opp Queue-kallet.

Falle 3: UI-Callback triggert Reentrancy

Nokre gonger er sjølve UI-oppdateringa ikkje «uskyldig»: å sette eigenskapar kan utløyse hendingar (OnChange, OnResize) som igjen startar logikk som aksesserer Worker-tilstandar. Dette er ikkje eit deadlock i snever forstand, men fører til vanskelegforklarte hengingar og race conditions. Tiltak: legg UI-oppdateringar i «stille» spor (deaktiver hendingar midlertidig) eller bruk reentrancy-guards (t.d. ein atomar guard for oppdateringsfasen).

Falle 4: Zu viele queued Updates

Sjølv utan waits kan UI «fryse» dersom du genererer titusenvis av queued callbacks. Symptom: ProgressBar heng etter, vindauget reagerer treigt, CPU-bruken i Main Thread aukar. Løysing: drossle (tidsvindauge), aggregere (teljar), eller ei ekte Producer/Consumer-struktur der berre éi UI-oppdatering kan vere «pending» (Coalescing).

Når det blir meir komplisert: Ergebnisse sammeln, Fehler bündeln, Reihenfolgen garantieren

TParallel.For er ideell når iterasjonane er uavhengige. I forretningsprogramvare er iterasjonane ofte berre «weitgehend» uavhengige: dei les filer, kallar REST-APIs, skriv databaselinjer. Då må du planleggje tre ekstra punkt nøye:

  • Trådsikker Ergebnis-Sammlung: Enten per tråd ein lokal buffer (am Ende zusammenführen) eller eine threadsichere Queue/Collection. Locks im Hot Path vermeiden.
  • Feilhandsaming: Exceptions aus Worker-Threads müssen eingesammelt werden. I praksis fungerer dette godt: merk den første Exception og utløys ein Cancel, eller samle alle Exceptions og vis dei samla ved slutten.
  • Rekkjefølgje: Dersom utdata krev stabil rekkjefølgje (t. d. loggar etter indeks), er parallell prosessering med seinare sortering ofte enklare enn „trådssikker ordna innsetjing“.

For UI inneber dette: vis ikkje kvar enkelt feilmelding med ein gong. Det fører til modal-dialog-helvete. Samle feil (t. d. ei liste med strengar), og vis til slutt ei samanfatting eller ein eksporterbar logg.

Debugging: Korleis du gjer deadlock verkeleg synleg

Deadlocks i parallellkode er frustrerande, fordi dei kan sjå annleis ut i debugger enn i release. Likevel finst det nokre praktiske verkemiddel:

Bruk Threads-vinduet og kallstakkar

Når UI heng, sjå på alle trådane: kvar står Main Thread? Ventar han? Er han i ei Message-Loop? Kvar står Worker-Threads? Dersom Worker i Synchronize heng, er årsaka nesten alltid „Main Thread blockiert“ eller „Main Thread braucht einen Lock“.

Queue-/Synchronize-stader markere

Set målretta logging ved overleveringsstader (før Queue, i Queue-Callback, på slutten av iterasjonen). I drift er dette ofte meir verdifullt enn Breakpoints, fordi timing er avgjerande. Pass på at logginga sjølv er trådssikker og ikkje blokkerande (t. d. ingen UI-Log-Ausgabe direkte aus Worker-Threads).

Mål siste oppdateringstid

Når UI «heng», kan det enkelt vere at ho må handtere for mange oppdateringar. Mål derfor i Main Thread kor ofte per sekund du utfører eit UI-Update og kor lang tid det tek. Når UI-Callbacks tek meir enn nokre millisekund, er drossling eller forenkling nødvendig.

Når er TParallel.For med Progress-UI verkeleg lønsamt?

Parallelisering er ikkje eit mål i seg sjølv. Ho er særleg lønsam når:

  • iterasjonane er store nok (millisekund til sekund), slik at Threadpool-Overhead vert oppvegd,
  • jobben er CPU-intensiv (parsing, komprimering, hashing) eller har I/O som lett let seg parallellisere (fleire filer, fleire HTTP-Requests med grenser),
  • det finst ein klar Cancel- og feilhandsamingsstrategi,
  • UI-krava kan nøye seg med aggregert framdrift.

Ho er mindre lønsam når kvar iterasjon er ekstremt kort (mikro-operasjonar) eller når alle iterasjonar køyrer inn i same flaskehals (ein seriel DB-Transaktion, ein global Lock, ei enkelt fil). Då er oftast det raskaste grepet: forbetre algoritmen, batche, redusere datatilgang eller eksplisitt løyse opp flaskehalsen.

Praksis-sjekkliste: Slik held UI-en seg stabil

  • Main Thread ikkje blokkert: inga Waits, inga lange loops utan Message Pump.
  • Worker rører aldri Controls: inga VCL/FMX-Zugriffe utanfor UI-Thread.
  • UI-Updates er drossla: Counter/Timer eller koalescerte Queue-Updates i staden for per iterasjon.
  • Ingen Synchronize-Aufrufe frå innanfor locks.
  • Cancel er kooperativt, blir ofte kontrollert og ryddar opp skikkeleg.
  • Exceptions blir samla og handtert ordna til ved slutten.

Konklusjon: Entkoppel med Queue, stabiliser med aggregering

Ei robust framdrifts‑UI ved TParallel.For oppstår ikkje ved å «putte Synchronize inn ein stad», men ved eit klart arkitekturprinsipp: workerane arbeider uavhengig, hovudtråden held seg fri og behandlar berre få, raske UI‑oppdateringar. TThread.Queue er eit eigna verkty her når du brukar det målretta og drossla. Ved treigt eller ustabilt åtferd skuldast det nesten alltid to ting: hovudtråden ventar blokkerande nokre stadar — eller han druknar i oppdateringar som ligg i kø.

Når du har sett mønsteret ordentleg opp (Counter/Coalescing, Cancel-Flag, avslutnings‑callback), løner det seg på mange stader i ein veksande Delphi‑applikasjon: import/eksport, datavalideringar, fil‑ og API‑jobbar — alt blir meir responsivt, utan at du med kvar framdriftsoppdatering får nye deadlocks.

Hvis du treng støtte til å stabilisere parallellkode, til å feilsøkje UI‑hengingar eller til ryddig modernisering av veksande Delphi‑applikasjonar: ta kontakt.

Til dette temaet høyrer òg Delphi Parallel Programming Library og Tthread.queue Vs Synchronize. Innlegget set desse aspekta inn i ein forståeleg samanheng og viser kva som tel i den daglege drifta.

Drøft prosjekt eller moderniseringsprosjekt med Net-Base.

neste steg

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

Vi støttar ikkje berre ved enkeltspørsmål, men òg når korte kildekodesnuttar, legacy-tema eller portalidéar skal utviklast til eit robust bedriftsprosjekt.

  • Eksisterande tilstand, målbiletet og tekniske risikoar blir vurderast samla.
  • REST, Datenzugriff, Portale und Rollout werden nicht als Spätfolgen verschoben.
  • De ser tidleg kva veg som er økonomisk og driftsmessig berekraftig.

Del innlegg

Del dette innlegget direkte

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

E-post

Instagram opnar i ein ny fane. Lenkje og kort tekst blir kopiert til utklippstavla på førehand.