Wer in Delphi mit Threads arbeitet, landet früher oder später bei TThread.Synchronize. Und genau dort passieren die unangenehmen Dinge: sporadische Hänger, „UI reagiert nicht“, scheinbar zufällige Deadlocks beim Beenden oder beim Öffnen eines Dialogs. Der Kern ist selten „Delphi ist kaputt“, sondern fast immer ein ungünstiger Mix aus Synchronize, blockierenden Warteoperationen und einem UI-Thread, der seine Message Loop (die Ereignisverarbeitung der VCL) nicht mehr sauber abarbeitet. Dieser Beitrag zeigt robuste, im Legacy-Kontext praktikable Muster für TThread und Synchronize ohne UI-Deadlocks – inklusive Timeout-Variante, sauberer Fehlerweitergabe, Shutdown-Regeln und Debugging-Hinweisen, die in echten Bestandsanwendungen helfen.
WaitFor sa snáithe UI, a luaithe a bhíonn cosán sa worker a úsáideann Synchronize.TThread.Queue nó trí dispatcher ionas nach gcuirtear bac ar an worker.TThread.Queue is minic an rogha réamhshocraithe is fearr: cuireann an worker obair chuig an Snáithe Príomh, leanann sé ar aghaidh ag rith agus ní gcuirtear bac air. Cuireann sé sin cosc ar go leor deadlocks. Ní réitíonn sé áfach gach cás imeallach — mar shampla má theastaíonn toradh ó oibrí go práinneach a ghintear sa Snáithe Príomh (m.sh. rochtain ar acmhainn nasctha leis an UI nó comhpháirt atá ceangailte le snáithe amháin).
TThread agus Synchronize gan deadlocks sa UI: samhail intinne do sheachadtaí glanta
Samhail iontaofa chun oibriú leo: níl ach cúpla seachadadh síncrónach dlisteanach chuig an Snáithe Príomh. Is éard eile go bunúsach ná stádas, léiriú nó teileaméireolaíocht — agus mar sin, ba cheart iad a dhéanamh asíncrónach.
Cabhraíonn aicmiú simplí le linn athbhreithnithe agus le cobhsú tionscadal atá ann cheana:
- „Taispeáin amháin”: dul chun cinn, líne logála, comhaire, soilse stádais, gníomhachtú/neamhghníomhachtú — i gcónaí
Queue. - „Stádas a tharchur”: cuireann an worker réad sonraí/DTO ar fáil, déanann an UI é a léiriú —
Queue, ach le cóip agus le neamh-athraitheacht (gan struchtúir a roinntear agus a athraítear go comhthreomhar). - „Caithfidh an UI cinneadh”: Ní hamháin anseo a bhíonn seasmhacht síncrónach riachtanach (m.sh. fiosrúchán úsáideora). Ansin is í an cheist chriticiúil: an gá don worker fanacht i ndáiríre, nó an féidir an sreabhadh oibre a athdhearadh (Meaisín Stáit, post a chur ar ceal, é a atosú níos déanaí)?
Is í an tríú catagóir an duine is lú coincheapúil le haghaidh deadlocks: má tá an worker ag fanacht ar thoradh ón UI, is féidir go spreagfar an UI chun fanacht ar an worker (nó go ndéantar é go hindíreach trí locks). Tarlaíonn sé sin go háirithe faoi ualach, le bunachair sonraí mall nó i dtimpeallachtaí Remote Desktop.
Sliotán foinsí: UI-dispatcher le Queue, Timeout roghnach agus dúnadh glan
Cuimsíonn an patrún thíos seachadtaí chuig an UI i rang chabhrach bheag. Faigheann tú:
- Post: seol-agus-ná-fan trí
TThread.Queue(gnáthchás do nuashonruithe stádais). - Call: glao síncrónach le Timeout (neamhghnách, ach cabhrach i gcásanna legacy), gan
Synchronizea úsáid mar phointe blocála go díreach. - Cosaint dúnadh: postanna UI nua a dhiúltú, agus seiceálann poist atá i Queue bratach sula n-idirghníomhaítear le rialuithe.
Catagóiriú teicniúil: úsáidimid Queue in éineacht le TEvent (imeacht Kernel) mar aiseolas. Ní fhánann an worker ar Synchronize, ach ar imeacht a shocraítear sa Snáithe Príomh tar éis don ghníomh atá sa Queue a bheith curtha i gcrích. Déanann an Timeout cosc ar ghreamú gan deireadh má theipeann ar an snáithe UI, ar chúis ar bith, tascanna a phróiseáil.
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.Cuspóir an chóid agus cá háit a bhfuil sé go hintinneach „neamhghnách”
Ní athraíonn an patrún Synchronize é go hiomlán, ach déanann sé aistrithe síncrónacha a dhéanamh inrialaithe: ní bhíonn an snámhthráid oibre ag fanacht ar mheicnic Synchronize, ach ar imeacht. Leis seo is féidir leat teorainneacha ama (timeouts) a éileamh, a léiriú le linn an oibriúcháin má tá an snámhthráid UI greamaithe, agus i gcéim dúnadh (shutdown) postanna UI nua a dhiúltú go comhsheasmhach.
Coinníollacha teoranta agus deacrachtaí
- Infheictheacht na cuimhne:
DoneEventis ea an chomhartha sioncrónaithe. Mar sin, tá léamhRaisedObjtar éisWaitForcomhsheasmhach. Mar sin féin, ba chóir go bhfanfadhRaisedObjáitiúil do gach glao (mar anseo), agus níor chóir é a dhéanamh domhanda.
AcquireExceptionObject coiscíonn nach n-imíonn an eisceacht sa phríomh-snáithe. Nuair a athmhaireann tú í sa snáithe oibre, ní bhíonn an stacktrace go hiomlán comhoiriúnach leis an bhfréamh, ach fanann an teachtaireacht earráide sa log an snáithí oibre, agus féadann an jab teip go glan.BeginShutdown ba chóir é a chur i seicheamh dúnta lárnach (m.sh. go han-luath i OnCloseQuery na príomhfhoirme). Mura bhfuil, cuirfear UI-jobs sa chiseal go fóill agus beidh fuinneoga scriosta cheana féin.Lock-Strategie: so vermeiden Sie Lock-Inversionen mit UI-Callbacks
Is iondúil nach gceaptar an chuid is mó deadlocks ó WaitFor, ach ó ordlathas lock neamhshoiléir. Sreabhghníomh tipiciúil: blocálann snáithe oibre an múnla sonraí, glaonn sé nuashonrú UI trí Synchronize, agus téann an nuashonrú UI ar ais chuig an múnla sonraí. Tá sé seo intuigthe ó thaobh loighce, ach tá sé dochreidte ó thaobh teicniúil.
Rialacha praiticiúla a oibríonn go maith i bhfoirne:
- Keine Locks über Thread-Grenzen halten: Sula gcuirfear rud ar bith i dtreo an UI ó snáithe oibre (queued/synchronized), ba chóir na locks ghairmiúla a scaoileadh.
- UI liest Snapshots: Ba cheart nach bhféachann UI-Callbacks “beo” isteach i struchtúir an snáithí oibre; taispeánadh cóipeanna/snapshots ina ionad (m.sh. DTO, Record, luachanna simplí).
- Logging ist ein Lock-Kandidat: Má úsáideann logging inmheánach ciseal, file-lock nó singleton, d’fhéadfadh sé a bheith mar chuid de deadlock. Ba chóir do UI-Callbacks logging a choinneáil íosta nó scríobh trí phíblíne logála ar leith, neamhbhacach.
Más rud é go bhfuil ailtireacht Layer-3 agat cheana (UI, Services/Domäne, bonneagar cosúil le rochtain sonraí): ba chóir do UI-Callbacks, más féidir, ach gníomhaíochtaí UI a dhéanamh. Níor chóir go mbeadh aon rud atá “Service” sa callback. Laghdaíonn sin éifeachtaí reentrancy go suntasach.
Shutdown ohne Hänger: „nicht WaitFor, sondern kooperatives Stoppen“
Le linn dúnadh bíonn sé coitianta go dtitfidh rudaí as a chéile: dúntaíonn an UI, ba chóir go mbeadh snáithe imithe, ach tá UI-jobs fós sa chiseal. Is beag choreagrafaíocht é shutdown glan seachas “snáithe a mharú”:
- Shutdown-Flag setzen (m.sh.
TUiDispatcher.BeginShutdown): Ón nóiméad sin, gan aon UI-jobs nua a ghlacadh. - Worker kooperativ stoppen: Seiceálann an snáithe oibre bratach chealaithe (m.sh.
TEventnó rud cosúil leTCancellationToken) agus críochnaíonn sé lúbanna/feithimh go comhoibritheach. - UI nicht blockieren: Ná húsáid lúb chrua feithimh sa phríomh-snáithe. Má tá ort fanacht, déan é ach le Message Loop ag rith (nó níos fearr: seachain é go hiomlán trí chríochnú a láimhseáil trí callback).
- Letzte UI-Aufräumarbeiten ach amháin nuair atá fuinneoga/controls cinnte go fóill ann. I VCL tá an t-am tábhachtach: tráth nach déanaí ná nuair a imíonn an handle, níor chóir do jobs atá sa chiseal dul chuig controls níos mó.
Tá an sreabhadh seo tábhachtach do oibriú agus do thacaíocht: “Tá an fheidhmchlár greamaithe ag dúnadh” is fadhb glacála clasaiceach, cé go bhfuil próiseáil ghairmiúil déanta i gceart. Sábhálann shutdown sainmhínithe ama fíor anseo.
Debugging: Wie Sie den Deadlock greifbar machen (ohne Rätselraten)
Nuair a bhíonn sé greamaithe, is é an cheist lárnach: Cé atá ag fanacht ar cé? Cúpla cur chuige a fhianaíonn iad féin i dtionscadail reatha:
- Gach áit le fanúint a liostáil: Cuardach téacs iomlán do
WaitFor,Sleepi lúbanna,TEvent.WaitFor,INFINITE. Is minic gurb iad fanachtaí “i bhfolach” an fhreaschur (fiú i leabharlanna). - Staid snáithe sa log: Déan logáil ag teorainneacha snáithe: „Job startet“, „queued UI“, „UI ausgeführt“, „Job fertig“. Mar sin feicfidh tú an mbíonn an Main Thread ag próiseáil poist atá i Queue ar chor ar bith.
- Seiceáil an amhras Message-Loop: Má tharlaíonn an fionraí ach le haghaidh dialóga modúil nó idirghníomhartha COM ar leith, is minic gurb é an Message Loop an bacphointe. Sa chás sin is é an sprioc: laghdú ualaí ar láimhseálaithe UI, aonrú glaonna COM agus gan oibríochtaí fada a dhéanamh sa UI.
- Déan Locks le feiceáil: I gcás
TCriticalSection/TMonitoris fiú build dífhabhtaithe a úsáid le métadata „Owner“ (m.sh. Thread-ID ag Enter) agus tomhas ama. Mar sin feicfidh tú cén Lock atá á choinneáil ag an Main Thread faoi láthair, fad is atá worker-threads ag fanacht ar an UI.
Tábhachtach ná an intinn: Bíonn Deadlocks ann go hannamh mar tharlaí “randamacha”. Is ciorcail dothuartha iad go hiomlán, a spreagann go hinmhinic iad féin go hanannamh. Nuair atá an ciorcal sin idirdheargaithe go soiléir, is minic gurb é an bealach réitigh atá soiléir freisin.
Leaganacha do rochtain sonraí agus poist comhéadan (FireDAC, REST, córas comhad)
Go háirithe i gcás FireDAC (nó rochtain DB eile) tá sé fíorthábhachtach a thuiscint: i bpráictis bíonn an nasc, an idirbheart agus na Datasets go nasctha le snáithe. Ba chóir do snáithe oibre a gcomhthéacs DB a shealbhú go heisiach. Ba chóir glaonna UI a theorannú don léiriú amháin, ní do oibríochtaí DB. Is patrún láidir é:
- An snáithe oibre a rith query/glao REST, an toradh a ríomh agus DTO a ghiniúint.
- An snáithe oibre an DTO a phostáil chuig an UI trí
Queue/TUiDispatcher.Post. - Glacann an UI an DTO agus nuashonraítear na Controls (gan filleadh ar rudaí an snáithe oibre).
Má tá foirmeacha meascaithe fhorbartha go stairiúil agat („UI spreagann DB, DB-Callback spreagann UI“), tá sé loighciúil dul i ngleic leo céim ar chéim: ar dtús na pointí trasna a aontú/wanáil (Dispatcher), ansin stáit a laghdú isteach i Services/Model. Tá sé níos lú rioscaí ná athchóiriú mór, agus laghdaíonn sé Deadlocks go suntasach.
Conclúid: Deadlocks a sheachaint = Rialú ar an t-aistriú
TThread agus Synchronize gan UI-Deadlocks is ní mar dheicíocht aon-theicniúil amháin ach mar smacht: bacainní a íoslaghdú, ord na locks a choinneáil slán, Shutdown a shonrú agus spleáchais UI síncrónacha a theorannú. Tá an UI-Dispatcher léirithe úsáideach i staidéanna Legacy toisc go n-úsáideann sé Queue mar réamhshocrú, ach cuireann sé Timeout agus rialacha Shutdown shoiléire i bhfeidhm do na haistriúcháin síncrónacha atá riachtanach.
Tá teorainneacha réitithe: má tá an Main Thread blocáilte go buan (de bharr loighic UI trom, slabhraí dialóg modúil nó glaonna COM-STA), ní féidir leis an Dispatcher ach diagnóis a dhéanamh agus stop rialaithe a chur i bhfeidhm. Is í an réiteach inbhuanaithe ná an UI a éadromú agus freagrachtaí a scaipeadh. Má theastaíonn uait tacaíocht i bhfeidhmchlár Delphi reatha — ó ghabháil i snáitheoirí go cobhsú céimnithe — is féidir an tionscadal a phlé agus a chur in ord anseo: tionscadal nó tionscnamh nua-aimseartha le Net-Base.
I gcur chuige gairmiúil tá ról tábhachtach ag Delphi Multithreading agus Synchronize Deadlock nuair is gá go n-oibreodh comhtháthú, sreafaí sonraí agus forás le chéile go glan.