Min jaħdem ma‘ threads f‘Delphi, aktar tard jew kmieni jinqeda fuq TThread.Synchronize. U eżatt hemm iseħħu l-affarijiet mhux pjaċevoli: konġelamenti sporadiċi, „UI ma tirrispondix“, deadlocks li jidherom każwali waqt it-twettiq jew waqt il-ftuħ ta‘ dialog. Il-kawża prinċipali rari tkun „Delphi hu miksur“, iżda kważi dejjem hija kombinazzjoni inċonvenjenti ta‘ Synchronize, operazzjonijiet ta‘ stennija li jibbloġġjaw u UI-Thread li ma jikkonsmax il- Message Loop (l-ipproċessar tal-avvenimenti tal-VCL) b’mod nadif. Dan il-post juri mudelli robusti u prattiċi fil-kuntest legacy għal TThread u Synchronize mingħajr UI-Deadlocks – inkluż varjant bil-timeout, trasferiment nadif ta‘ żbalji, regoli ta‘ shutdown u suġġerimenti għall-debugging li jgħinu f’applikazzjonijiet ekzistenti.
Għaliex jinqalgħu deadlocks madwar Synchronize fil-prattika
Synchronize tfisser: worker-thread jaqleb proċedura f’queue li titħaddem fil-Main Thread, u jistenna tipikament sakemm dik il-proċedura tkun tlestiet. F’applikazzjonijiet VCL il-Main Thread huwa fl-istess ħin il-UI-Thread (tiri, controls, avvenimenti). Barra minn hekk, f’ħafna installazzjonijiet hemm oġġetti COM taħt il-STA-Modell (Single-Threaded Apartment: is-sejħiet COM jeħtieġu li jiġu pproċessati fil-istess thread), li jsaħħu l-dependenza fuq Message Loop li jaħdem b’mod ġust.
Deadlocks normalment jseħħu minħabba waħda minn dawn il-konfigurazzjonijiet:
- WaitFor fil-Main Thread: Il-UI-Thread jistenna għal worker (eż.
MyThread.WaitFor), filwaqt li l-worker bħalissa jeħtieġ lill-UI-Thread permezz ta‘Synchronize. It-tnejn jistennew – tmiem. - Lock-Inversion: Il-worker iżomm lock (eż.
TCriticalSectionjewTMonitor) u jagħmel sejħa għalSynchronize. Il-proċedura sinċronizzata fuq il-UI tipprova tieħu l-istess lock (direttament jew indirettament, spiss permezz ta‘ logging/cache/singletons) – deadlock klassiku. - Shutdown/Destroy: Meta tiġi magħluqa form, thread jiġi interrott filwaqt li għad hemm kompiti
Synchronizependenti. Partikolarment perikoluż: sejħiet sinċronizzati jirreferu għall-controls li qed jiġu distrutti. - Message Loop blockiert: Dialogs modali, operazzjonijiet UI li jdumu twil, sejħa COM li tibbloġġja jew handler li „mal eben“ jagħmel DB/REST iżommu l-Main Thread bl-addoċċ. Il-kompiti
Synchronizejiġu eżegwiti b’rtard jew mhux eżegwiti kompletament.
L-ikbar konsegwenza għall-arkitettura u l-operazzjonijiet: Synchronize huwa saff ta‘ blokk. F’softwer aziendali individwali b’importazzjonijiet, BDE-sostituzzjoni b’konnessjoni nattiva-queries, jobs ta‘ interfaċċa jew servizzi ta‘ sfond b’komponent UI, dan is-saff għandu jkun ikkontrollat b’mod konsċju – inkella ‚rarament‘ jista‘ jsir ‚dejjem, partikolarment meta jkun hemm urġenza‘.
Regola bażika: Il-UI-Thread qatt m’għandu jistenna fuq worker (jekk Synchronize qiegħed jintuża)
Jekk xi worker juża Synchronize, il-Main Thread m’għandux jistenna b’mod qawwi u jibbloġġja fuq dan il-worker. Dan jista‘ jidhir banali, iżda fil-kodiċi legacy huwa waħda mill-ikbar kawżi, għax il-prattiċi bħal „nistennew ftit waqt il-għeluq“ jew „dialog tal-progress jistenna l-konklużjoni“ jiġu implimentati faċilment.
Konsegwenzi prattiċi:
WaitFor-Aufrufe fil-UI-Thread, ladarba fil-Worker jeżisti triq li tuża Synchronize.TThread.Queue jew dispatcher, sabiex il-Worker ma jinqabadx.TThread.Queue spiss huwa l-aħjar default: il-Worker jippubblika xogħol lill-Main Thread, ikompli jaħdem u ma jibbloġġjax. Dan jipprevjeni ħafna deadlocks. Ma jsolvix però l-kasijiet limiti – pereżempju meta fil-Worker għandek bżonn zwingend riżultat li jinħolqot fil-Main Thread (eż. aċċess għal riżorsa marbuta mal-UI jew komponent li huwa threadgebunden).
TThread und Synchronize ohne UI-Deadlocks: Denkmodell für saubere Übergaben
Mudell ta‘ ħsieb robust huwa: hemm ftit biss trasferimenti sinkroni leġittimi lejn il-Main Thread. Kollox ieħor huwa status, preżentazzjoni jew telemetrija — u għalhekk asinkroniku.
Diviżjoni sempliċi tgħin fir-reviżjonijiet u fis-stabbilizzazzjoni ta‘ proġetti eżistenti:
- „Biss wiri“: indikatur tal-progress, linja tal-log, għadd, semafor, attiva/disattiva – dejjem
Queue. - „Tgaddi l-istat“: il-Worker jipprovdi oġġett tad-data/DTO, l-UI tirrenderja –
Queue, iżda b’kopja/immutability (allura l-ebda strutturi li jitbiddlu flimkien). - „Il-UI trid tiddeċiedi“: hawn biss tinħtieġ semantika sinkrona (eż. mistoqsija lill-utent). Imbagħad il-mistoqsija ewlenija hi: jeħtieġ tassew li Worker jistenna, jew jista‘ jiġi ristrutturat il-workflow (State Machine, ikkanċella job, kompli aktar tard)?
Speċjalment it-tielet kategorija hija qafas ta‘ deadlock: jekk il-Worker jistenna riżultat tal-UI, il-UI spiss tkun mċaqalqa biex tistenna lill-Worker (jew indirettament permezz ta‘ locks). Dan jaffettwa ħafna aktar taħt il-piż, ma‘ databases bil-mod jew f’ambjenti Remote-Desktop.
Source-Schnipsel: UI-Dispatcher mit Queue, optionalem Timeout und sauberem Shutdown
Il-mudell li ġej jinżamm f’klassi assistiva żgħira għal trasferimenti lejn il-UI. Tiksbu:
- Post: fire-and-forget permezz ta‘
TThread.Queue(tipiku għal aġġornamenti tal-istatus). - Call: sejħa sinkrona b‘ Timeout (mhux komuni, imma utli f’sitwazzjonijiet legacy), mingħajr ma tuża direttament
Synchronizebħala punt ta‘ blokk. - Shutdown-Schutz: ma taċċettax aktar UI-jobs ġodda, u l-jobs fil-queue jiċċekkjaw flag qabel ma jmissu Controls.
Klassifikazzjoni teknika: nużaw Queue flimkien ma‘ TEvent (Kernel-Event) għall-feedback. Il-Worker ma jistenna mhux fuq Synchronize, iżda fuq event li jiġi stabbilit fil-Main Thread wara li l-azzjoni fil-queue tkun eżegwita. It-Timeout jipprevjeni stallo ‚li jdum għal dejjem‘ jekk il-UI-Thread, għal xi raġuni, ma jkunx qed jerġa‘ jagħmel il-ħruġ.
unit UiDispatch;
interface
uses
System.SysUtils,
System.Classes,
System.SyncObjs;
type
EUiDispatchTimeout = class(Exception);
EUiDispatchShuttingDown = class(Exception);
/// <summary>
/// Kapselt UI-Aufrufe aus Worker-Threads.
/// Post: asynchron (Queue).
/// Call: synchron mit Timeout, ohne TThread.Synchronize direkt zu blocken.
/// </summary>
TUiDispatcher = class
strict private
class var FShuttingDown: Integer;
public
class procedure BeginShutdown; static;
class function IsShuttingDown: Boolean; static;
class procedure Post(const AProc: TProc); static;
class procedure Call(const AProc: TProc; ATimeoutMs: Cardinal = 5000); static;
end;
implementation
{ TUiDispatcher }
class procedure TUiDispatcher.BeginShutdown;
begin
TInterlocked.Exchange(FShuttingDown, 1);
end;
class function TUiDispatcher.IsShuttingDown: Boolean;
begin
Result := TInterlocked.CompareExchange(FShuttingDown, 0, 0) = 1;
end;
class procedure TUiDispatcher.Post(const AProc: TProc);
begin
if not Assigned(AProc) then
Exit;
// Im Shutdown keine neuen UI-Jobs mehr annehmen.
if IsShuttingDown then
Exit;
// Queue blockiert den Worker nicht.
TThread.Queue(nil,
procedure
begin
if IsShuttingDown then
Exit;
AProc();
end);
end;
class procedure TUiDispatcher.Call(const AProc: TProc; ATimeoutMs: Cardinal);
var
DoneEvent: TEvent;
RaisedObj: TObject;
begin
if not Assigned(AProc) then
Exit;
if IsShuttingDown then
raise EUiDispatchShuttingDown.Create('UI-Dispatcher ist im Shutdown.');
DoneEvent := TEvent.Create(nil, True, False, '');
try
RaisedObj := nil;
TThread.Queue(nil,
procedure
begin
try
if not IsShuttingDown then
AProc();
except
// Exception-Objekt über die Thread-Grenze reichen.
// Achtung: Kein "raise" hier, sonst landet es im Main Thread.
RaisedObj := AcquireExceptionObject;
end;
DoneEvent.SetEvent;
end);
case DoneEvent.WaitFor(ATimeoutMs) of
wrSignaled:
begin
if Assigned(RaisedObj) then
raise Exception(RaisedObj);
end;
wrTimeout:
raise EUiDispatchTimeout.CreateFmt(
'Timeout nach %d ms: Main Thread hat UI-Aufruf nicht abgearbeitet.',
[ATimeoutMs]);
else
raise Exception.Create('Unerwarteter WaitFor-Status im UI-Dispatcher.');
end;
finally
DoneEvent.Free;
end;
end;
end.L-iskop tal-kodiċi u fejn hu b’mod konxju ‚mhux konvenzjonali‘
Dan il-mudell ma jissostitwixxix kompletament Synchronize, imma jagħmel il-passaggi sinkroni kontrollabbli: il-worker ma jistenniex għall-mekkanika ta‘ Synchronize, iżda jistenna event. B’dan il-mod tista‘ tforza timeouts, tagħti viżibilità fl-operazzjoni li l-UI-Thread huwa mblukkat, u fi fażi ta‘ shutdown tirrifjuta b’mod konsekuttiv kompiti UI ġodda.
Il-parti ‚mhux konvenzjonali‘ mhix l-Event, iżda d-deċiżjoni li tirrappreżenta s-semantika sinkrona permezz ta‘ Queue + Event. Dan jagħmel sens speċifikament meta jkollok bżonn ittejjeb l-istabbiltà gradwalment f’applikazzjonijiet eżistenti mingħajr ma tiddekonstruixxi immedjatament kull punt ta‘ Synchronize fuq livell arkitettoniku.
Kundizzjonijiet u ostakli
- Viżibilità tal-memorja:
DoneEventhuwa l-punt ta‘ sinkronizzazzjoni. Dan jagħmel il-qari ta‘RaisedObjwaraWaitForkonsistenti. MadankolluRaisedObjgħandu jibqa‘ lokali għal kull chiamata (bħal hawn), qatt globali.
AcquireExceptionObject jipprevjeni li l-eċċezzjoni fil-Main Thread „tisparixxi“. Meta jerġa‘ jittieħed fil-Worker il-Stacktrace mhuwiex identiku għall-oriġini, iżda l-messaġġ ta‘ żball jibqa‘ fil-log tal-Worker, u l-job jista‘ jonqos b’mod nadif.BeginShutdown għandu jkun parti minn sekwenza ċentrali ta‘ Shutdown (z. B. ħafna kmieni f‘OnCloseQuery tal-forma prinċipali). Inkella UI-Jobs għadhom jiġu queued waqt li twieqi jkunu diġà mħassra.Strategija tal-lock: kif tevita inversioni tal-lock bil-UI-Callbacks
Ħafna deadlocks ma jiġux minħabba WaitFor, iżda minħabba ordni ta‘ lock mhux ċara. Fl-iskennjar tipiku: il-Worker jagħmel lock fuq il-mudell tad-dejta, jisħaq aġġornament tal-UI permezz ta‘ Synchronize, u l-aġġornament tal-UI jerġa‘ jwaqqaf aċċess għall-mudell tad-dejta. Dan huwa loġiku, iżda teknikament fatali.
Regoli prattiċi li jistgħu jiġu adottati fit-timijiet:
- M’għandekx iżżomm locks bejn il-fruntieri tat-thread: Qabel ma‘ Worker joġġeġġa jew jis-sinkronizza xi ħaġa lejn il-UI, il-locks funzjonali għandhom ikunu rilaxxati.
- Il-UI taqra snapshots: Il-UI-Callbacks m’għandhomx jaraw „live“ f’strutturi tal-Worker, iżda għandhom juru kopji/snapshots (z. B. DTO, Record, valuri sempliċi).
- Il-logging jista‘ jkun kandidat għall-lock: Jekk il-logging juża internament queue, darba-lock fuq fajl jew singleton, jista‘ jsir parti minn deadlock. Il-UI-Callbacks għandhom iżommu l-logging għall-minimu jew jiktebuh fuq pipeline tal-log separata u mhux blokkanti.
Jekk diġà għandek arkitettura Layer-3 (UI, Services/Domäne, infrastruttura bħal aċċess tad-dejta): idealment il-UI-Callbacks għandhom jagħmlu biss il-UI. Kollox li hu „Service“ m’għandhiex tkun fil-callback. Dan jnaqqas b’mod sinifikanti l-effetti ta‘ reentrancy.
Shutdown mingħajr tfixkil: „mhux WaitFor, iżda waqfien kooperattiv“
Fil-ħin tat-tmiem spiss jinqalgħu problemi: il-UI tagħlaq, thread għandu jitlaq, iżda UI-Jobs fil-queue għadhom miftuħa. Shutdown nadif mhux tant «qerda tat-thread», imma żgħira koreografija:
- Poġġi flag ta‘ shutdown (z. B.
TUiDispatcher.BeginShutdown): Minn issa ‚l quddiem ebda UI-Jobs ġodda. - Waqqaf il-Worker b’mod kooperattiv: Il-Worker jivverifika cancel-flag (z. B.
TEventjew similiTCancellationToken) u jtemm ċikli/Waits. - La tibblokka l-UI: Ebda loop ta‘ stennija qawwi fil-Main Thread. Jekk trid tistenna, nagħmlu dan biss b’loop tal-messaġġi li jkompli jaħdem (jew aħjar: evita kompletament billi ttratta l-kompletament permezz ta‘ callback).
- L-aħħar kompiti ta‘ tindif tal-UI biss meta twieqi/controls huma garantiti li għadhom jeżistu. Fil-VCL il-ħin huwa kruċjali: sa l-aħħar mument, meta l-handle ikun imneħħi, jobs queued m’għandhomx imorru fuq il-controls.
Dan il-proċess huwa rilevanti għall-operazzjoni u l-appoġġ: „L-applikazzjoni tissieq fil-ħin tal-għeluq“ hija problema klassika ta‘ aċċettazzjoni, anki meta l-proċessi teknċi ġew immaniġġjati korrettament. Shutdown definitiv jiffranka hawn ħin reali.
Debugging: Kif tagħmel il-deadlock viżibbli (mingħajr tbassir)
Jekk ikun hemm stallo, il-mistoqsija ewlenija hija: Min jistenna min? Xi approċċi li juru effett f’proġetti eżistenti:
- Inventarjar ta’ kull Wait-post: Tfittxija fulltext għal
WaitFor,Sleepfi loopijiet,TEvent.WaitFor,INFINITE. Ħafna problemi huma “Waits” moħbija (anke f’biblioteki). - Stato tat-thread fil-log: Ittajpu l-log fil-fruntieri tat-thread: „Job beda“, „UI fil-queue“, „UI eżegwita“, „Job lest“. B’hekk tara jekk il-Main Thread qed jaħdem verament il-jobs li jkunu mqiegħda fil-queue.
- Iċċekkja suspett ta’ Message Loop: Jekk il-freeze jseħħ biss f’djalogi modali jew f’ċerti interazzjonijiet COM, spiss il-Message Loop huwa l-punt ta’ strozzjatura. L-għan f’dak il-każ: tnaqqis tal-ħidma fuq il-UI-handlers, isolazzjoni tas-sejħiet COM, u ebda operazzjonijiet twal fil-UI.
- Agħmel Locks viżibbli: Għal
TCriticalSection/TMonitorjiswa build ta’ debug b’metadati “Owner” (eż. Thread-ID waqt il-Enter) u kejl taż-żmien. B’hekk tara liema lock il-Main Thread qiegħed iżomm fil-mument, filwaqt li worker jistenna l-UI.
Importanti hi l-atteġġjament: Deadlocks mhux spiss iseħħu “b’każ”. Huma ċikli deterministiċi li rari jiġu attivati. Ladarba tiddetermina l-ċiklu b’mod ċar, it-tiswija ġeneralment tkun evidenti.
Varjanti għall-aċċess tad-dejta u għall-jobs tal-interface (FireDAC, REST, sistema tal-fajls)
Speċjalment ma’ FireDAC (jew aċċessi DB oħra) huwa validu: konnessjoni, transazzjoni u datasets f’prattika huma marbutin ma’ thread. Worker-thread għandu jkollu esklussivament il-kuntest DB tiegħu stess. Sejħiet mill-UI għandhom jiffokaw fuq il-preżentazzjoni, mhux fuq operazzjonijiet DB. Mudell robust huwa:
- Il-worker iwettaq Query/REST-call, jikkalkula r-riżultat u joħloq DTO.
- Il-worker jippubblika l-DTO permezz ta’
Queue/TUiDispatcher.Postlejn il-UI. - Il-UI tieħu l-DTO u taġġorna l-controls (mingħajr ma tirreferi għal oġġetti tal-worker).
Jekk għandek forom miksija li nibtgħu historikament (“UI triggert DB, DB-Callback triggert UI”), jiswa disentangling gradwali: l-ewwel isolazzjoni tal-punti ta’ trasferiment (Dispatcher), imbagħad il-migrazzjoni tal-istati fi Services/Model. Dan huwa inqas riskjuż minn rimodellar kbir u jnaqqas id-deadlocks b’mod sinifikanti.
Konklużjoni: Evitar deadlocks ifisser kontroll tal-trasferimenti
TThread u Synchronize mingħajr UI-deadlocks mhix teknika waħda biss imma dixxiplina: minimizza blokkaġġi, żomm l-ordni tal-locks, iddefinixxi shutdown u inqas dipendenzi sinkroniċi fuq l-UI. L-UI-Dispatcher muri huwa partikolarment utli f’sitwazzjonijiet legacy, għax juża Queue bħala default, u għall-passijiet sinkroniċi meħtieġa jżid Timeout u regoli ċari ta’ shutdown.
Jibqa’ limitu tal-użu: Jekk il-Main Thread jibqa’ kontinwament imblukkjat (minħabba loġika UI tqila, katini ta’ djalogi modali jew sejħiet COM-STA), anke dispatcher jista’ biss jiddijanjostika u jeżegwixxi abort kontrollat. Is-soluzzjoni sostenibbli hi tnaqqis tal-piż fuq il-UI u t-taqsim tat-tmexxija tar-responsabilitajiet. Jekk hawn bżonn ta’ appoġġ f’applikazzjoni eżistenti Delphi – mill-imħażen tat-threading sa stabilizzazzjoni gradwali – tista’ torganizza dan il-proġett jew il-modernizzazzjoni: diskussjoni tal-proġett jew tal-modernizzazzjoni ma’ Net-Base.
Fil-kuntest professjonali, ukoll Delphi Multithreading u Synchronize Deadlock għandhom rwol importanti, meta l-integrazzjonijiet, il-flussi tad-dejta u l-iżvilupp kontinwu għandhom jikkoperaw b’mod nadif.
Niddiskutu proġett jew iniziattiva ta’ modernizzazzjoni ma’ Net-Base.