{"id":230,"date":"2015-04-16T17:25:00","date_gmt":"2015-04-16T06:25:00","guid":{"rendered":"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/?p=230"},"modified":"2015-04-16T17:25:00","modified_gmt":"2015-04-16T06:25:00","slug":"how-to-wait-on-multiple-events-delphi","status":"publish","type":"post","link":"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/archives\/230","title":{"rendered":"How to wait on multiple events (Delphi)"},"content":{"rendered":"<h1>Abstract<\/h1>\n<p>How can one write code to block (wait) on multiple syncro events at once, in a multi-threaded application? We are of course, talking about Delphi. By &#8220;multiple synchro events at once&#8221;, I mean that the thread unblocks on some logical combination of the statuses of the member events. Here I use the word &#8220;event&#8221; in a very general sense, not to be confused with the more specific sense of the word event, as in Embarcadero&#8217;s &#8220;TEvent&#8221;. In the general sense, by &#8220;event&#8221; or &#8220;synchro event&#8221;, I mean the broad class of synchro objects like semaphore (TSemaphore) and event (TEvent).<\/p>\n<p>The typical and most common logical combination is the &#8220;OR&#8221; operation. In other words, how can we block on multiple synchro events and be unblocked when the first event is signaled, or a time-out occurs. When we are finally unblocked, how can we know which event was the signaled one?<\/p>\n<h2>The Task<\/h2>\n<p>Write some re-usable library code, in Delphi XE7+, to block on multiple synchro events at once, with time-out. The code must be:<\/p>\n<ul>\n<li>cross-platform,<\/li>\n<li>Bullet proof and<\/li>\n<li>Have a really simple API.<\/li>\n<\/ul>\n<p>The wait function should allow for optional time-out, detect which entity caused the signal, and should leverage operating system capabilities for optimal efficiency.<\/p>\n<p>In this post, I will focus a particular subset of this task, where:<\/p>\n<ul>\n<li>all the synchro events are semaphores (eg. TSemaphore, but not TEvent)<\/li>\n<li>access to the semaphores is restricted to this library. In other words, they are unnamed.<\/li>\n<li>The wait condition is simply just to wait on the chronologically first semaphore to be signaled, or a time-out to occur, whichever first.<\/li>\n<\/ul>\n<p>However, I will also address, in less detail, the more general task.<\/p>\n<h2>By Why?<\/h2>\n<p>One of the big bug-bears in multi-threaded programming is that if a thread is blocked on some semaphore for normal operational purposes, then it can&#8217;t check if it is time to gracefully shut-down until it is unblocked. Unblocking driven by the semaphore being signaled. But if it is time to gracefully shutdown, then it is quiet likely that the semaphore, in which it is blocked on will never again be signaled, precisely because it is time to shut-down. For this reason, a lot of multi-threaded applications (whether written in Delphi or not) have problems shutting down gracefully. I call this the &#8220;block-and-check-terminate&#8221; problem. Some readers may be quick to respond, that this is a non-issue. With &#8220;proper&#8221; design, your programs will never have a &#8220;block-and-check-terminate&#8221; problem. While this may well be true, in my experience, I have found that in real applications, designing to avoid &#8220;block-and-check-terminate&#8221; problems, without a generic soltion, is complex and tedious.<\/p>\n<p>What is needed, is a generic cross-platform solution.<\/p>\n<h1>What have others done<\/h1>\n<h3>Jedi<\/h3>\n<p>The Jedi Component Library, in it&#8217;s multi-thread component code, provided a wrapper for thread and semaphore. It associated a special semaphore with each thread. The thread semaphore would start life as unsignaled. A call to terminate the thread would cause the special semaphore to be signaled. Whenever the thread would wait on some regular semaphore, the wrap would be defined in such a way that it would call the windows <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/ms687025%28v=vs.85%29.aspx\" title=\"WaitForMultipleObjects\" target=\"_blank\">WaitForMultipleObjects()<\/a> function on two semaphores: the explicit semaphore, and the thread&#8217;s special semaphore. If the thread was terminated while it was blocked on some operational semaphore, the thread would unblock, detect the condition and properly handle it.<\/p>\n<p>It was a elegant solution to the most common subset of our task. The problem is though, that it only works for Windows. Android, iOS and OSX have no o\/s API equivalent to WaitForMultipleObjects(). I don&#8217;t know why. Maybe mobile applications just have less need for within-app parallelism? If you search StackOverflow, there are more than a few questions asking how to wait on multiple semaphores, on Android or iOS, and there are no real solutions given. This is just my opinion. If you disagree, please post a comment below. <\/p>\n<h3>Just Kill threads<\/h3>\n<p>I&#8217;ve seen this done. When the program needs to shut-down, it just kills its non-main threads with an explicit kill command. Any self-respecting developer will be very uncomfortable with the solution, and it&#8217;s degree of safety is very situational.<\/p>\n<h3>Design Around<\/h3>\n<p>Design around the issue, so that no thread ever has to wait on multiple conditions. Good luck with that.<\/p>\n<h3>Design Above<\/h3>\n<p>Use a threading library, like <a href=\"http:\/\/otl.17slon.com\/index.htm\" title=\"Omni Thread Library\" target=\"_blank\">OTL<\/a>, to provide sufficient higher level structures to support parallel tasking, that there is no design need for lower level structures such as semaphores and events. You could do this, but it is a bit limiting. I think even with OTL, you will still run into requirements where you need to wait on multiple semaphores.<\/p>\n<h3>Count-down Latch<\/h3>\n<p><a href=\"http:\/\/stackoverflow.com\/questions\/5117778\/\" target=\"_blank\">This SO post<\/a>, suggests using a TCountdownEvent object (SyncObjs unit). When either of the component semaphores is signaled, also signal the count-down latch. To wait on the first of either, wait on the latch. The limitations of this solution are:<br \/>\nThe code that signals the component semaphores has to know about the latch rules. That&#8217;s not a general solution.<br \/>\nWhat happens when something wants to wait directly on a component semaphore? The relationship between the component semaphores and the latch is destroyed. So you either have to have code to deal with this (complex), or the latch solution is just used in situations where the only consumer of either semaphore is the consumer of both. Another problem is that it may be inefficient (CPU-wise) for Windows.<\/p>\n<h1>Offered Solution<\/h1>\n<p>The solution that I offer has a story that goes like this &#8230;<\/p>\n<p>Take the synchro classes that you want to use (TSemaphore, TEvent etc), and wrap them so that you take control of their WaitFor() and Signal() methods.<\/p>\n<p>Imagine a new object, called a &#8220;Condition&#8221;. A condition is a syncro object which is a composite view of an arbitrary set of member synchro objects. This is the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Composite_pattern\" target=\"_blank\">Composite Pattern<\/a> applied to synchro objects. The condition is considered signaled according to some rule of your design, based on the signal status of the members. For example, it could be deemed signaled, when one or more members are signaled, but clear when all members are clear. Apply a rule, that you cant directly signal the condition, because that would be meaningless.<\/p>\n<p>The condition is implemented by a private semaphore. When a member semaphore is signaled, the aforementioned condition semaphore is signaled (or not, if you have a different composition rule). When a member semaphore is successfully waited on, from code outside of the condition, the condition status needs to be updated atomically. When the condition is waited for, and unblocked, resource counts need to be decremented from the contributing signaler.<\/p>\n<p>Construction, destruction and operation of the condition needs to happen transparently, from direct operations on the member semaphores. We achieve this by each condition keeping a record of its member semaphores and their contribution to the resource count; and also, each member semaphore keeping a record of the list of conditions that it is entangled in. Construction and destruction of conditions needs to safely and correctly update entanglement lists.<\/p>\n<p>A single critical section (Gate), is shared by all synchro objects and conditions that might be entangled together. The gate must be passed in as a construction parameter.<\/p>\n<p>When the operating system call WaitForMultipleObjects() is available, it is used instead of the private condition semaphore. This call will be available if and only if:<\/p>\n<ol>\n<li>the operating system is win32\/64; and<\/li>\n<li>all member objects are descendants of THandleObject (and thus have a windows &#8220;handle&#8221;); and<\/li>\n<li>the client specifies so via a construction parameter.<\/li>\n<\/ol>\n<h1>The Full Source Code<\/h1>\n<p>Listing 1 below shows a solution for the aforementioned subset of the task. To implement other conditions, other than &#8220;unblock on first chronological member&#8221;, override the TSyncroCondition methods marked as virtual.<\/p>\n<pre class=\"brush: delphi; title: Listing_1:_SBD.TL.SyncObjs2; notranslate\" title=\"Listing_1:_SBD.TL.SyncObjs2\">\r\nunit SBD.TL.SyncObjs2;\r\ninterface\r\nuses System.SyncObjs, Generics.Collections, SysUtils;\r\n\r\nconst\r\n  Forever = System.INFINITE;\r\n  TimeOut = cardinal( $FFFFFFFF);\r\n  WaitForError = 0;\r\n\r\ntype\r\n\r\n  TSynchoConditionList = class;\r\n\r\n  ISynchroCondition = interface;\r\n  TSBDSemaphore = class\r\n    private\r\n      FEntangledConditions: TSynchoConditionList;\r\n      FGate: TCriticalSection;\r\n      FCount: cardinal;\r\n      FMax: cardinal;\r\n      FBase: TSemaphore;\r\n\r\n      function  BaseWaitFor( TimeLimit: cardinal): TWaitResult;\r\n      procedure BaseSignal;\r\n      procedure WaitedVia_External;\r\n\r\n    public\r\n      constructor Create( Gate: TCriticalSection; InitialCount, MaxCount: cardinal);\r\n      destructor Destroy; override;\r\n      function  WaitFor( TimeLimit: cardinal): TWaitResult;\r\n      function  Signal: boolean;\r\n      function  Count: cardinal;\r\n      function  AsConditions( AConstrainToHandleSyncros: boolean): ISynchroCondition;\r\n    end;\r\n\r\n  ISynchroCondition = interface\r\n    &#x5B;'{CFD0DD74-6EB4-4CCF-9EE1-8BBAC759151A}']\r\n      function WaitFor( TimeLimit: cardinal; var Contributor: TSBDSemaphore): TWaitResult;\r\n      function Join( const Addend: ISynchroCondition): ISynchroCondition;\r\n    end;\r\n\r\n  IInterfaceHelper = interface\r\n    &#x5B;'{40D9B899-AF13-4FC1-AF04-23E8416E1FEE}']\r\n      function AsObject: TObject;\r\n  end;\r\n\r\n  TSyncroCondition = class( TInterfacedObject, ISynchroCondition, IInterfaceHelper)\r\n    protected\r\n      \/\/ Override these methods to implement conditions other that &quot;first chronological&quot;.\r\n      function  ConditionIsSignaled: boolean;                                    virtual;\r\n      function  ConditionWillBeSignaledAfterSignal( Sem: TSBDSemaphore):boolean; virtual;\r\n      function  ConditionWillBeSignaledAfterWait  ( Sem: TSBDSemaphore):boolean; virtual;\r\n      function  WaitCanUseOS_API: boolean;                                       virtual;\r\n      function  OS_API_WaitFor( TimeLimit: cardinal; var Contributor: TSBDSemaphore): TWaitResult; virtual;\r\n\r\n    private\r\n      FisSignalled: boolean;\r\n      FWillBeSignaled: boolean;\r\n      FSignals: TDictionary&lt;TSBDSemaphore,cardinal&gt;;\r\n      FGate: TCriticalSection;\r\n      FisBroken: boolean;\r\n      FBase: TSemaphore;\r\n      FInited: boolean;\r\n      FConstrainedToAllHandleSyncros: boolean;\r\n\r\n      constructor CreateWithOne( Origin: TSBDSemaphore; AConstrainToHandleSyncros: boolean);\r\n      destructor Destroy; override;\r\n\r\n      procedure Presignal ( Sem: TSBDSemaphore);\r\n      procedure Postsignal( Sem: TSBDSemaphore);\r\n      procedure PostWait  ( Sem: TSBDSemaphore);\r\n      procedure BaseSignal;\r\n      procedure BaseWaitForever;\r\n      function  BaseWaitFor( TimeLimit: cardinal): TWaitResult;\r\n      function  WaitFor( TimeLimit: cardinal; var Contributor: TSBDSemaphore): TWaitResult;\r\n      function  FindASignallingContributor( var Sem: TSBDSemaphore): boolean;\r\n      procedure NotifyMemberDestroyed( Member: TSBDSemaphore);\r\n      function  AsObject: TObject;\r\n      function  Join( const Addend: ISynchroCondition): ISynchroCondition;\r\n      procedure CheckInit;\r\n    end;\r\n\r\n  TSynchoConditionList = class( TList&lt;TSyncroCondition&gt;)\r\n    public\r\n    end;\r\n\r\nimplementation\r\n\r\n\r\n\r\n\r\nfunction TSBDSemaphore.Count: cardinal;\r\nbegin\r\nresult := FCount\r\nend;\r\n\r\nconstructor TSBDSemaphore.Create( Gate: TCriticalSection; InitialCount, MaxCount: cardinal);\r\nbegin\r\nAssert( MaxCount &gt;= 1);\r\nAssert( InitialCount &lt;= MaxCount);\r\nFGate := Gate;\r\nFBase := TSemaphore.Create( nil, InitialCount, MaxCount, '', False);\r\nFEntangledConditions := TSynchoConditionList.Create;\r\nFCount := InitialCount;\r\nFMax   := MaxCount\r\nend;\r\n\r\ndestructor TSBDSemaphore.Destroy;\r\nvar\r\n  Cnd: TSyncroCondition;\r\nbegin\r\nFGate.Enter;\r\ntry\r\n  for Cnd in FEntangledConditions do\r\n    Cnd.NotifyMemberDestroyed( self);\r\n  FBase.Free\r\nfinally\r\n  FGate.Leave\r\n  end;\r\ninherited\r\nend;\r\n\r\nfunction TSBDSemaphore.Signal: boolean;\r\nvar\r\n  Cnd: TSyncroCondition;\r\n  Saturated: boolean;\r\nbegin\r\nFGate.Enter;\r\ntry\r\n  Saturated := FCount &gt;= FMax;\r\n  if not Saturated then\r\n    begin\r\n    Inc( FCount);\r\n    for Cnd in FEntangledConditions do\r\n      Cnd.Presignal( self)\r\n    end;\r\n  BaseSignal;\r\n  if not Saturated then\r\n    for Cnd in FEntangledConditions do\r\n      Cnd.Postsignal( self);\r\nfinally\r\n  FGate.Leave\r\n  end;\r\nend;\r\n\r\nprocedure TSyncroCondition.Presignal( Sem: TSBDSemaphore);\r\nbegin\r\nAssert( FGate = Sem.FGate);\r\nCheckInit;\r\nFWillBeSignaled := ConditionWillBeSignaledAfterSignal( Sem);\r\nFSignals&#x5B; Sem] := FSignals&#x5B; Sem] + 1;\r\nif (not FisSignalled) and FWillBeSignaled then\r\n  BaseSignal;\r\nend;\r\n\r\nprocedure TSyncroCondition.BaseWaitForever;\r\nbegin\r\nBaseWaitFor( Forever)\r\nend;\r\n\r\nconstructor TSyncroCondition.CreateWithOne( Origin: TSBDSemaphore; AConstrainToHandleSyncros: boolean);\r\nbegin\r\nFisBroken := False;\r\nFGate     := Origin.FGate;\r\nFConstrainedToAllHandleSyncros := AConstrainToHandleSyncros;\r\n{$IFDEF MSWINDOWS}\r\nif FConstrainedToAllHandleSyncros then\r\n  Assert( Origin.FBase is THandleObject);\r\n{$ENDIF MSWINDOWS}\r\nFInited   := False;\r\nFSignals  := TDictionary&lt;TSBDSemaphore,cardinal&gt;.Create;\r\nFSignals.Add( Origin, Origin.FCount);\r\nFisSignalled    := False;\r\nFWillBeSignaled := False;\r\nFBase := nil\r\nend;\r\n\r\nprocedure TSyncroCondition.CheckInit;\r\nvar\r\n  InitialCount: cardinal;\r\nbegin\r\nif FInited then exit;\r\nFInited := True;\r\nFisSignalled    := ConditionIsSignaled;\r\nFWillBeSignaled := FisSignalled;\r\nif FisSignalled then\r\n    InitialCount := 0\r\n  else\r\n    InitialCount := 1;\r\nif not WaitCanUseOS_API then\r\n  FBase := TSemaphore.Create( nil, InitialCount, 1, '', False)\r\nend;\r\n\r\n\r\ndestructor TSyncroCondition.Destroy;\r\nvar\r\n  Member: TSBDSemaphore;\r\nbegin\r\nFGate.Enter;\r\ntry\r\n  FisBroken := True;\r\n  for Member in FSignals.Keys do\r\n    Member.FEntangledConditions.Remove( self);\r\n  FSignals.Free;\r\n  FBase.Free\r\nfinally\r\n  FGate.Leave\r\n  end;\r\ninherited\r\nend;\r\n\r\nprocedure TSyncroCondition.NotifyMemberDestroyed( Member: TSBDSemaphore);\r\nbegin\r\nif FGate &lt;&gt; Member.FGate then\r\n  FisBroken := True;\r\nif FisBroken then exit;\r\nFGate.Enter;\r\ntry\r\n  if FSignals.ContainsKey( Member) then\r\n    begin\r\n    FisBroken := True;\r\n    FSignals.Remove( Member)\r\n    end\r\nfinally\r\n  FGate.Leave\r\n  end\r\nend;\r\n\r\nfunction TSyncroCondition.OS_API_WaitFor(\r\n  TimeLimit: cardinal; var Contributor: TSBDSemaphore): TWaitResult;\r\n{$IFDEF MSWINDOWS}\r\nvar\r\n  HandleObjs: THandleObjectArray;\r\n  SignaledObj: THandleObject;\r\n  Member: TSBDSemaphore;\r\n  i: integer;\r\n{$ENDIF MSWINDOWS}\r\nbegin\r\n{$IFDEF MSWINDOWS}\r\n  SetLength( HandleObjs, FSignals.Count);\r\n  i := -1;\r\n  for Member in FSignals.Keys do\r\n    begin\r\n    Inc( i);\r\n    HandleObjs&#x5B; i] := Member.FBase as THandleObject\r\n    end;\r\n  result := THandleObject.WaitForMultiple( HandleObjs, TimeLimit, False, SignaledObj, False, 0);\r\n  if result = wrSignaled then\r\n    begin\r\n    i := -1;\r\n    for Member in FSignals.Keys do\r\n      begin\r\n      Inc( i);\r\n      if SignaledObj &lt;&gt; Member.FBase then continue;\r\n      Member.WaitedVia_External;\r\n      break\r\n      end\r\n    end\r\n{$ELSE}\r\n  result := wrError\r\n{$ENDIF MSWINDOWS}\r\nend;\r\n\r\nfunction TSyncroCondition.WaitCanUseOS_API: boolean;\r\nbegin\r\n{$IFDEF MSWINDOWS}\r\nresult := FConstrainedToAllHandleSyncros;\r\n{$ELSE}\r\nresult := False\r\n{$ENDIF MSWINDOWS}\r\nend;\r\n\r\nfunction TSyncroCondition.WaitFor(\r\n  TimeLimit: cardinal; var Contributor: TSBDSemaphore): TWaitResult;\r\nvar\r\n  TimeLeft: cardinal;\r\n  AfterWaitClock, Elapsed: TDateTime;\r\n  Confirmed: boolean;\r\n  doRetry: boolean;\r\n  Count: cardinal;\r\nbegin\r\nif FisBroken then\r\n  begin\r\n  result := wrAbandoned;\r\n  exit\r\n  end;\r\nCheckInit;\r\nif WaitCanUseOS_API then\r\n    result := OS_API_WaitFor( TimeLimit, Contributor)\r\n  else\r\n    begin\r\n    TimeLeft := TimeLimit;\r\n    repeat\r\n      result := BaseWaitFor( TimeLeft);\r\n      if result &lt;&gt; wrSignaled then break;\r\n      if TimeLeft &gt; 0 then\r\n        AfterWaitClock := Now;\r\n      FGate.Enter;\r\n      try\r\n        Confirmed := FindASignallingContributor( Contributor);\r\n        if Confirmed then\r\n          begin\r\n          result    := Contributor.BaseWaitFor( 0);\r\n          Confirmed := result = wrSignaled\r\n          end;\r\n        doRetry := result = wrTimeOut;\r\n        if doRetry and (TimeLeft &gt; 0) then\r\n          begin\r\n          Elapsed := Trunc( (Now - AfterWaitClock) * MSecsPerDay);\r\n          if TimeLeft &gt; Elapsed then\r\n              Dec( TimeLeft, Elapsed)\r\n            else\r\n              TimeLeft := 0\r\n          end;\r\n        if Confirmed then\r\n          begin\r\n          Count := FSignals&#x5B; Contributor];\r\n          if Count &gt; 0 then\r\n            FSignals&#x5B; Contributor] := Count - 1\r\n          end;\r\n        if ConditionIsSignaled then\r\n          BaseSignal;\r\n      finally\r\n        FGate.Leave\r\n        end;\r\n      if doRetry then\r\n        Sleep(1)\r\n    until not doRetry\r\n    end\r\nend;\r\n\r\nprocedure TSyncroCondition.Postsignal( Sem: TSBDSemaphore);\r\nbegin\r\nCheckInit;\r\nif FisSignalled and (not FWillBeSignaled) then\r\n  BaseWaitForever\r\nend;\r\n\r\nprocedure TSBDSemaphore.WaitedVia_External;\r\nvar\r\n  Cnd: TSyncroCondition;\r\n  Saturated: boolean;\r\nbegin\r\nFGate.Enter;\r\ntry\r\n  Saturated := FCount = 0;\r\n  if not Saturated then\r\n    begin\r\n    Dec( FCount);\r\n    for Cnd in FEntangledConditions do\r\n      Cnd.PostWait( self)\r\n    end\r\nfinally\r\n  FGate.Leave\r\n  end;\r\nend;\r\n\r\nfunction TSBDSemaphore.WaitFor( TimeLimit: cardinal): TWaitResult;\r\nbegin\r\nresult := BaseWaitFor( TimeLimit);\r\nif result = wrSignaled then\r\n  WaitedVia_External\r\nend;\r\n\r\n\r\nprocedure TSyncroCondition.PostWait( Sem: TSBDSemaphore);\r\nvar\r\n  WR: TWaitResult;\r\n  Saturated: boolean;\r\n  Count: cardinal;\r\nbegin\r\nAssert( FGate = Sem.FGate);\r\nAssert( not FisBroken);\r\nCheckInit;\r\nFWillBeSignaled := ConditionWillBeSignaledAfterWait( Sem);\r\nCount           := FSignals&#x5B; Sem];\r\nif Count &gt; 0 then\r\n  FSignals&#x5B; Sem] := Count - 1;\r\nif FisSignalled &lt;&gt; FWillBeSignaled then\r\n  begin\r\n  if FWillBeSignaled then\r\n      BaseSignal\r\n    else\r\n      BaseWaitForever\r\n  end\r\nend;\r\n\r\n\r\nfunction TSBDSemaphore.AsConditions( AConstrainToHandleSyncros: boolean): ISynchroCondition;\r\nbegin\r\nresult := TSyncroCondition.Create( self, AConstrainToHandleSyncros);\r\nend;\r\n\r\nprocedure TSBDSemaphore.BaseSignal;\r\nbegin\r\nFBase.Release\r\nend;\r\n\r\nfunction TSBDSemaphore.BaseWaitFor( TimeLimit: cardinal): TWaitResult;\r\nbegin\r\nresult := FBase.WaitFor( TimeLimit)\r\nend;\r\n\r\n\r\nfunction TSyncroCondition.FindASignallingContributor(\r\n  var Sem: TSBDSemaphore): boolean;\r\nvar\r\n  Pair: TPair&lt;TSBDSemaphore,cardinal&gt;;\r\nbegin\r\nresult := not FisBroken;\r\nif not result then exit;\r\nresult := False;\r\nfor Pair in FSignals do\r\n  begin\r\n  result := Pair.Value &gt; 0;\r\n  if not result then continue;\r\n  Sem := Pair.Key;\r\n  break\r\n  end\r\nend;\r\n\r\nfunction TSyncroCondition.AsObject: TObject;\r\nbegin\r\nresult := self\r\nend;\r\n\r\nfunction TSyncroCondition.Join(\r\n  const Addend: ISynchroCondition): ISynchroCondition;\r\nvar\r\n  Composite, Friend: TSyncroCondition;\r\n  Pair: TPair&lt;TSBDSemaphore,cardinal&gt;;\r\n  InitialCount: cardinal;\r\nbegin\r\nFGate.Enter;\r\ntry\r\n  Composite := TSyncroCondition.Create;\r\n  result    := Composite;\r\n  Friend    := (Addend as IInterfaceHelper).AsObject as TSyncroCondition;\r\n  Assert( FGate = Friend.FGate);\r\n  Composite.FisBroken := FisBroken or Friend.FisBroken;\r\n  Composite.FGate     := FGate;\r\n  FSignals            := TDictionary&lt;TSBDSemaphore,cardinal&gt;.Create;\r\n  for Pair in FSignals do\r\n    Composite.FSignals.Add( Pair.Key, Pair.Value);\r\n  for Pair in Friend.FSignals do\r\n    begin\r\n    if Composite.FSignals.ContainsKey( Pair.Key) then\r\n        Composite.FSignals&#x5B; Pair.Key] := Composite.FSignals&#x5B; Pair.Key] + Friend.FSignals&#x5B; Pair.Key]\r\n      else\r\n        Composite.FSignals.Add( Pair.Key, Pair.Value)\r\n    end;\r\n  for Pair in  Composite.FSignals do\r\n    if Pair.Key.FEntangledConditions.IndexOf( self) = -1 then\r\n      Pair.Key.FEntangledConditions.Add( self);\r\n  Composite.FisSignalled    := ConditionIsSignaled;\r\n  Composite.FWillBeSignaled := FisSignalled;\r\n  Composite.FInited         := False;\r\n  Composite.FBase           := nil;\r\n  {$IFDEF MSWINDOWS}\r\n  Assert( FConstrainedToAllHandleSyncros = Friend.FConstrainedToAllHandleSyncros);\r\n  {$ENDIF MSWINDOWS}\r\n  Composite.FConstrainedToAllHandleSyncros := FConstrainedToAllHandleSyncros;\r\nfinally\r\n  FGate.Leave\r\n  end;\r\nend;\r\n\r\nprocedure TSyncroCondition.BaseSignal;\r\nbegin\r\nif assigned( FBase) then\r\n  FBase.Release;\r\nFisSignalled := True\r\nend;\r\n\r\nfunction TSyncroCondition.BaseWaitFor( TimeLimit: cardinal): TWaitResult;\r\nbegin\r\nif assigned( FBase) then\r\n    result := FBase.WaitFor( TimeLimit)\r\n  else\r\n    result := wrError;\r\nFisSignalled := False\r\nend;\r\n\r\n\r\nfunction TSyncroCondition.ConditionIsSignaled: boolean;\r\nvar\r\n  Pair: TPair&lt;TSBDSemaphore,cardinal&gt;;\r\nbegin\r\nresult := False;\r\nfor Pair in FSignals do\r\n  begin\r\n  result := Pair.Value &gt; 0;\r\n  if not result then continue;\r\n  break\r\n  end\r\nend;\r\n\r\nfunction TSyncroCondition.ConditionWillBeSignaledAfterSignal(\r\n  Sem: TSBDSemaphore): boolean;\r\nbegin\r\nresult := True\r\nend;\r\n\r\nfunction TSyncroCondition.ConditionWillBeSignaledAfterWait(\r\n  Sem: TSBDSemaphore): boolean;\r\nvar\r\n  Pair: TPair&lt;TSBDSemaphore,cardinal&gt;;\r\n  Count, Sum: cardinal;\r\nbegin\r\nresult := False;\r\nSum    := 0;\r\nfor Pair in FSignals do\r\n  begin\r\n  Count := Pair.Value;\r\n  if (Pair.Key = Sem) and (Count &gt; 0) then\r\n    Dec( Count);\r\n  Inc( Sum, Count);\r\n  result := Sum &gt; 0;\r\n  if not result then continue;\r\n  break\r\n  end\r\nend;\r\n\r\n\r\nend.\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Abstract How can one write code to block (wait) on multiple syncro events at once, in a multi-threaded application? We are of course, talking about Delphi. By &#8220;multiple synchro events at once&#8221;, I mean that the thread unblocks on some &hellip; <a href=\"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/archives\/230\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[10],"tags":[],"class_list":["post-230","post","type-post","status-publish","format-standard","hentry","category-delphi"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p2QXbt-3I","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/wp-json\/wp\/v2\/posts\/230","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/wp-json\/wp\/v2\/comments?post=230"}],"version-history":[{"count":8,"href":"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/wp-json\/wp\/v2\/posts\/230\/revisions"}],"predecessor-version":[{"id":238,"href":"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/wp-json\/wp\/v2\/posts\/230\/revisions\/238"}],"wp:attachment":[{"href":"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/wp-json\/wp\/v2\/media?parent=230"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/wp-json\/wp\/v2\/categories?post=230"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/seanbdurkin.id.au\/pascaliburnus2\/wp-json\/wp\/v2\/tags?post=230"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}