Net-Base Iris

15.05.2026

TThread agus Synchronize gan blocálacha san UI: patrúin chobhsaí do VCL agus cód legacy

Conas oibriú go iontaofa le TThread, Synchronize agus Queue gan an UI a ghreamú: cúiseanna tipiciúla Deadlock, patrún UI-dispatcher praiticiúil (lena n-áirítear Timeout), cosaint i gcoinne Shutdown, straitéisí Lock agus seiceálacha Debugging do fheidhmchláir Delphi atá fásaithe.

15.05.2026

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.

  • Níl aon ghlaonna WaitFor sa snáithe UI, a luaithe a bhíonn cosán sa worker a úsáideann Synchronize.
  • Seol signáil maidir le críochnú snáithe trí Event/Callback: fanann an UI freagairteach, agus ní dhéanann sé glanadh suas ach tar éis an chomhartha.
  • Postáil nuashonruithe UI de ghnáth trí 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 Synchronize a ú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.

    Delphi
    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: DoneEvent is ea an chomhartha sioncrónaithe. Mar sin, tá léamh RaisedObj tar éis WaitFor comhsheasmhach. Mar sin féin, ba chóir go bhfanfadh RaisedObj áitiúil do gach glao (mar anseo), agus níor chóir é a dhéanamh domhanda.
  • Exception-Handling: 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.
  • Timeout ist Diagnose und Schutz: Ní dhearna sé aon “deisiú” ar phríomh-snáithe atá blocáilte. Ach cuireann sé cosc ar snáitheanna oibre acmhainní a ghabháil gan teorainn (m.sh. BDE-Ablosung mit nativer Anbindung-idirbheart a choinneáil oscailte), agus déanann sé an rang earráide intomhaiste agus tomhais.
  • Shutdown muss früh beginnen: 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ú”:

    1. Shutdown-Flag setzen (m.sh. TUiDispatcher.BeginShutdown): Ón nóiméad sin, gan aon UI-jobs nua a ghlacadh.
    2. Worker kooperativ stoppen: Seiceálann an snáithe oibre bratach chealaithe (m.sh. TEvent nó rud cosúil le TCancellationToken) agus críochnaíonn sé lúbanna/feithimh go comhoibritheach.
    3. 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).
    4. 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, Sleep i 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/TMonitor is 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 é:

    1. An snáithe oibre a rith query/glao REST, an toradh a ríomh agus DTO a ghiniúint.
    2. An snáithe oibre an DTO a phostáil chuig an UI trí Queue/TUiDispatcher.Post.
    3. 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.

    Tionscadal nó tionscnamh nua-aimseartha le Net-Base a phlé.

    Roinn an post

    Roinn an t-alt seo go díreach

    Tá LinkedIn, X, XING, Facebook, WhatsApp agus ríomhphost ar fáil láithreach. Do Instagram ullmhaímid nasc agus téacs gairid láithreach.

    Ríomhphost

    Osclaítear Instagram i gcluaisín nua. Cóipeáiltear an nasc agus an téacs gairid roimh ré isteach sa ghearrthaisce.