IdSync.pas 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.13 03/16/05 11:15:42 AM JSouthwell
  18. Named the IdNotify thread for simpler debugging.
  19. Rev 1.12 2004.04.13 10:22:52 PM czhower
  20. Changed procedure to class method.
  21. Rev 1.11 4/12/2004 11:44:36 AM BGooijen
  22. fix
  23. Rev 1.10 4/12/2004 11:36:56 AM BGooijen
  24. NotifyThread can be cleaned up with procedure now
  25. Rev 1.9 2004.03.11 10:14:46 AM czhower
  26. Improper cast fixed.
  27. Rev 1.8 2004.02.29 8:23:16 PM czhower
  28. Fixed visibility mismatch.
  29. Rev 1.7 2004.02.25 10:11:42 AM czhower
  30. Fixed visibility in notify
  31. Rev 1.6 2004.02.03 4:16:54 PM czhower
  32. For unit name changes.
  33. Rev 1.5 1/1/2004 11:56:10 PM PIonescu
  34. Fix for TIdNotifyMethod's constructor
  35. Rev 1.4 2003.12.31 7:33:20 PM czhower
  36. Constructor bug fix.
  37. Rev 1.3 5/12/2003 9:17:42 AM GGrieve
  38. compile fix
  39. Rev 1.2 2003.09.18 5:42:14 PM czhower
  40. Removed TIdThreadBase
  41. Rev 1.1 05.6.2003 ã. 11:30:12 DBondzhev
  42. Mem leak fix for notifiers created in main thread. Also WaitFor for waiting
  43. notification to be executed.
  44. Rev 1.0 11/13/2002 09:00:10 AM JPMugaas
  45. }
  46. unit IdSync;
  47. // Author: Chad Z. Hower - a.k.a. Kudzu
  48. interface
  49. {$i IdCompilerDefines.inc}
  50. {$IFDEF HAS_STATIC_TThread_ForceQueue}
  51. {$IFDEF BROKEN_TThread_ForceQueue}
  52. {$UNDEF HAS_STATIC_TThread_ForceQueue}
  53. {$ENDIF}
  54. {$ENDIF}
  55. {$UNDEF NotifyThreadNeeded}
  56. {$IFNDEF HAS_STATIC_TThread_Synchronize}
  57. {$DEFINE NotifyThreadNeeded}
  58. {$ENDIF}
  59. {$IFNDEF HAS_STATIC_TThread_Queue}
  60. {$DEFINE NotifyThreadNeeded}
  61. {$ELSE}
  62. {$IFNDEF HAS_STATIC_TThread_ForceQueue}
  63. {$DEFINE NotifyThreadNeeded}
  64. {$ENDIF}
  65. {$ENDIF}
  66. uses
  67. Classes,
  68. IdGlobal
  69. {$IFDEF NotifyThreadNeeded}
  70. , IdThread
  71. {$ENDIF}
  72. ;
  73. type
  74. TIdSync = class(TObject)
  75. protected
  76. {$IFNDEF HAS_STATIC_TThread_Synchronize}
  77. FThread: TIdThread;
  78. {$ENDIF}
  79. //
  80. procedure DoSynchronize; virtual; abstract;
  81. public
  82. {$IFDEF HAS_STATIC_TThread_Synchronize}
  83. constructor Create; virtual;
  84. {$ELSE}
  85. constructor Create; overload; virtual;
  86. constructor Create(AThread: TIdThread); overload; virtual;
  87. {$ENDIF}
  88. procedure Synchronize;
  89. class procedure SynchronizeMethod(AMethod: TThreadMethod);
  90. //
  91. {$IFNDEF HAS_STATIC_TThread_Synchronize}
  92. property Thread: TIdThread read FThread;
  93. {$ENDIF}
  94. end
  95. {$IFDEF HAS_STATIC_TThread_Synchronize}
  96. // TODO: deprecate TIdSync only if anonymous procedures are supported?
  97. // Delphi's TThread.Synchronize() supports them, but FreePascal's does not...
  98. {.$IFDEF HAS_STATIC_TThread_Synchronize_AnonProc}
  99. //{$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use static TThread.Synchronize() with an anonymous procedure'{$ENDIF}{$ENDIF}
  100. {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use static TThread.Synchronize()'{$ENDIF}{$ENDIF}
  101. {.$ENDIF}
  102. {$ENDIF}
  103. ;
  104. TIdNotify = class(TObject)
  105. protected
  106. FMainThreadUsesNotify: Boolean;
  107. //
  108. procedure DoNotify; virtual; abstract;
  109. {$IFNDEF USE_OBJECT_ARC}
  110. procedure InternalDoNotify;
  111. {$ENDIF}
  112. public
  113. constructor Create; virtual; // here to make virtual
  114. procedure Notify;
  115. {$IFNDEF HAS_STATIC_TThread_Queue}
  116. procedure WaitFor; {$IFDEF HAS_DEPRECATED}deprecated;{$ENDIF}
  117. {$ENDIF}
  118. class procedure NotifyMethod(AMethod: TThreadMethod; AForceQueue: Boolean = False);
  119. //
  120. property MainThreadUsesNotify: Boolean read FMainThreadUsesNotify write FMainThreadUsesNotify;
  121. end
  122. {$IFDEF HAS_STATIC_TThread_Queue}
  123. {$IFDEF HAS_STATIC_TThread_ForceQueue}
  124. // TODO: deprecate TIdNotify only if anonymous procedures are available?
  125. // Delphi's TThread.(Force)Queue() supports them, but FreePascal's does not...
  126. {.$IFDEF HAS_STATIC_TThread_Queue_AnonProc}
  127. //{$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use static TThread.Queue() or TThread.ForceQueue() with an anonymous procedure'{$ENDIF}{$ENDIF}
  128. {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use static TThread.Queue() or TThread.ForceQueue()'{$ENDIF}{$ENDIF}
  129. {.$ENDIF}
  130. {$ENDIF}
  131. {$ENDIF}
  132. ;
  133. {$I IdSymbolDeprecatedOff.inc}
  134. TIdNotifyMethod = class(TIdNotify)
  135. protected
  136. FMethod: TThreadMethod;
  137. //
  138. procedure DoNotify; override;
  139. public
  140. constructor Create(AMethod: TThreadMethod); reintroduce; virtual;
  141. end {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} {$IFDEF HAS_STATIC_TThread_Queue}{$IFDEF HAS_STATIC_TThread_ForceQueue}'Use static TThread.Queue() or TThread.ForceQueue()'{$ELSE}'Use static TThread.Queue()'{$ENDIF}{$ELSE}'Use TIdNotify.NotifyMethod()'{$ENDIF}{$ENDIF}{$ENDIF};
  142. {$I IdSymbolDeprecatedOn.inc}
  143. implementation
  144. uses
  145. //facilitate inlining only.
  146. {$IFDEF DOTNET}
  147. {$IFDEF USE_INLINE}
  148. System.Threading,
  149. {$ENDIF}
  150. {$ENDIF}
  151. {$IFDEF NotifyThreadNeeded}
  152. {$IFDEF HAS_UNIT_Generics_Collections}
  153. System.Generics.Collections,
  154. {$ENDIF}
  155. {$ENDIF}
  156. {$IFDEF VCL_2010_OR_ABOVE}
  157. {$IFDEF WINDOWS}
  158. Windows,
  159. {$ENDIF}
  160. {$ENDIF}
  161. {$IFDEF USE_VCL_POSIX}
  162. Posix.SysSelect,
  163. Posix.SysTime,
  164. {$ENDIF}
  165. SysUtils
  166. {$IFNDEF NotifyThreadNeeded}
  167. , IdThread
  168. {$ENDIF}
  169. ;
  170. // TODO: there is a bug in FireMonkey prior to XE7 where FMX.TApplication does
  171. // not assign a handler to the Classes.WakeMainThread callback (see QC #123579).
  172. // Without that, TThread.Synchronize() and TThread.Queue() will not do anything
  173. // if the main message queue is idle at the moment they are called!!! If the
  174. // main thread *happens* to receive a message at a later time, say from UI
  175. // activity, then they will be processed. But for a background process, we
  176. // cannot rely on that. Need an alternative solution for those versions of
  177. // FireMonkey...
  178. {$IFDEF NotifyThreadNeeded}
  179. type
  180. // This is done with a NotifyThread instead of PostMessage because starting
  181. // with D6/Kylix Borland radically modified the mechanisms for .Synchronize.
  182. // This is a bit more code in the end, but its source compatible and does not
  183. // rely on Indy directly accessing any OS APIs and performance is still more
  184. // than acceptable, especially considering Notifications are low priority.
  185. {$IFDEF HAS_GENERICS_TThreadList}
  186. TIdNotifyThreadList = TThreadList<TIdNotify>;
  187. TIdNotifyList = TList<TIdNotify>;
  188. {$ELSE}
  189. // TODO: flesh out to match TThreadList<TIdNotify> and TList<TIdNotify> for non-Generics compilers...
  190. TIdNotifyThreadList = TThreadList;
  191. TIdNotifyList = TList;
  192. {$ENDIF}
  193. TIdNotifyThread = class(TIdThread)
  194. protected
  195. FEvent: TIdLocalEvent;
  196. FNotifications: TIdNotifyThreadList;
  197. public
  198. procedure AddNotification(ASync: TIdNotify);
  199. constructor Create; reintroduce;
  200. destructor Destroy; override;
  201. class procedure FreeThread;
  202. procedure Run; override;
  203. end;
  204. var
  205. GNotifyThread: TIdNotifyThread = nil;
  206. procedure CreateNotifyThread;
  207. begin
  208. // TODO: this function has a race condition if it is called by multiple
  209. // threads at the same time and GNotifyThread has not been assigned yet!
  210. // Need to use something like InterlockedCompareExchangeObj() so any
  211. // duplicate threads can be freed...
  212. {
  213. Thread := TIdNotifyThread.Create;
  214. if InterlockedCompareExchangeObj(TObject(GNotifyThread), Thread, nil) <> nil then begin
  215. Thread.Free;
  216. end else begin
  217. Thread.Start;
  218. end;
  219. }
  220. if GNotifyThread = nil then begin
  221. GNotifyThread := TIdNotifyThread.Create;
  222. GNotifyThread.Start;
  223. end;
  224. end;
  225. {$ENDIF}
  226. { TIdSync }
  227. {$IFNDEF HAS_STATIC_TThread_Synchronize}
  228. constructor TIdSync.Create(AThread: TIdThread);
  229. begin
  230. inherited Create;
  231. FThread := AThread;
  232. end;
  233. {$ENDIF}
  234. constructor TIdSync.Create;
  235. begin
  236. {$IFDEF HAS_STATIC_TThread_Synchronize}
  237. inherited Create;
  238. {$ELSE}
  239. {$IFDEF DOTNET}
  240. inherited Create;
  241. CreateNotifyThread;
  242. FThread := GNotifyThread;
  243. {$ELSE}
  244. CreateNotifyThread;
  245. Create(GNotifyThread);
  246. {$ENDIF}
  247. {$ENDIF}
  248. end;
  249. procedure DoThreadSync(
  250. {$IFNDEF HAS_STATIC_TThread_Synchronize}
  251. AThread: TIdThread;
  252. {$ENDIF}
  253. SyncProc: TThreadMethod);
  254. begin
  255. {
  256. if not Assigned(Classes.WakeMainThread) then
  257. begin
  258. // TODO: if WakeMainThread is not assigned, need to force a message into
  259. // the main message queue so TApplication.Idle() will be called so it can
  260. // call CheckSynchronize():
  261. //
  262. // on Windows, call PostMessage() to post a WM_NULL message to the TApplication window...
  263. //
  264. // on OSX (and iOS?), call NSApp.sendEvent(???), but with what kind of event?
  265. //
  266. // on Android, what to do???
  267. // We can't put the message in the queue before calling TThread.Synchronize(),
  268. // as it might get processed before Synchronize() can queue the procedure.
  269. // Might have to use TThread.Queue() instead and wait on a manual TEvent...
  270. end else
  271. begin
  272. }
  273. // RLebeau 6/7/2016: there are race conditions if multiple threads call
  274. // TThread.Synchronize() on the same TThread object at the same time
  275. // (such as this unit's GNotifyThread object)...
  276. {$IFDEF HAS_STATIC_TThread_Synchronize}
  277. // Fortunately, the static versions of TThread.Synchronize() can skip the
  278. // race conditions when the AThread parameter is nil, so we are safe here...
  279. // RS-78837
  280. TThread.Synchronize(nil, SyncProc);
  281. {$ELSE}
  282. // However, in Delphi 7 and later, the static versions of TThread.Synchronize()
  283. // call the non-static versions when AThread is not nil, and the non-static
  284. // versions are not even close to being thread-safe (see QualityPortal #RSP-15139).
  285. // They share a private FSynchronize variable that is not protected from
  286. // concurrent access.
  287. //
  288. // In Delphi 6, TThread.Synchronize() is thread-safe UNLESS a synch'ed method
  289. // raises an uncaught exception, then there is a race condition on a private
  290. // FSynchronizeException variable used to capture and re-raise the exception,
  291. // so multiple threads could potentially re-raise the same exception object,
  292. // or cancel out another thread's exception before it can be re-raised.
  293. //
  294. // In Delphi 5, there are race conditions on private FSynchronizeException and
  295. // FMethod variables, making Synchronize() basically not thread-safe at all.
  296. //
  297. // So, in Delphi 5 and 6 at least, we need a way for TIdSync to synch
  298. // a method more safely. Thread.Queue() does not exist in those versions.
  299. //
  300. // At this time, I do not know if FreePascal's implementation of TThread
  301. // has any issues.
  302. //
  303. // TODO: We might need to expand TIdNotifyThread to handle both TIdSync and
  304. // TIdNotify from within its own context...
  305. //
  306. AThread.Synchronize(SyncProc);
  307. {$ENDIF}
  308. // end;
  309. end;
  310. procedure TIdSync.Synchronize;
  311. begin
  312. DoThreadSync(
  313. {$IFNDEF HAS_STATIC_TThread_Synchronize}FThread,{$ENDIF}
  314. DoSynchronize
  315. );
  316. end;
  317. class procedure TIdSync.SynchronizeMethod(AMethod: TThreadMethod);
  318. begin
  319. {$IFDEF HAS_STATIC_TThread_Synchronize}
  320. DoThreadSync(AMethod);
  321. {$ELSE}
  322. CreateNotifyThread;
  323. DoThreadSync(GNotifyThread, AMethod);
  324. {$ENDIF}
  325. end;
  326. { TIdNotify }
  327. constructor TIdNotify.Create;
  328. begin
  329. inherited Create;
  330. end;
  331. {$UNDEF USE_DoThreadQueue}
  332. {$IFDEF HAS_STATIC_TThread_Queue}
  333. {$DEFINE USE_DoThreadQueue}
  334. {$ENDIF}
  335. {$IFDEF HAS_STATIC_TThread_ForceQueue}
  336. {$DEFINE USE_DoThreadQueue}
  337. {$ENDIF}
  338. {$IFDEF USE_DoThreadQueue}
  339. procedure DoThreadQueue(QueueProc: TThreadMethod
  340. {$IFDEF HAS_STATIC_TThread_ForceQueue}
  341. ; AForceQueue: Boolean = False
  342. {$ENDIF}
  343. );
  344. begin
  345. {
  346. if not Assigned(Classes.WakeMainThread) then
  347. begin
  348. // TODO: if WakeMainThread is not assigned, need to force a message into
  349. // the main message queue so TApplication.Idle() will be called so it can
  350. // call CheckSynchronize():
  351. //
  352. // on Windows, call PostMessage() to post a WM_NULL message to the TApplication window...
  353. //
  354. // on OSX (and iOS?), call NSApp.sendEvent(???), but with what kind of event?
  355. //
  356. // on Android, what to do???
  357. // We can't put the message in the queue before calling TThread.Queue(),
  358. // as it might get processed before Queue() can queue the procedure.
  359. // Might have to wait on a manual TEvent...
  360. end else
  361. begin
  362. }
  363. {$IFDEF HAS_STATIC_TThread_ForceQueue}
  364. if AForceQueue then begin
  365. TThread.ForceQueue(nil, QueueProc);
  366. end else begin
  367. TThread.Queue(nil, QueueProc);
  368. end;
  369. {$ELSE}
  370. // TODO: FreePascal/Lazarus has Application.QueueAsyncCall(), but it is in the Forms unit!
  371. {
  372. if AForceQueue then begin
  373. Application.QueueAsyncCall(NotifyAsync, @QueueProc);
  374. else
  375. TThread.Queue(nil, QueueProc);
  376. }
  377. TThread.Queue(nil, QueueProc);
  378. {$ENDIF}
  379. // end;
  380. end;
  381. {$ENDIF}
  382. procedure TIdNotify.Notify;
  383. begin
  384. {$IFDEF HAS_STATIC_TThread_ForceQueue}
  385. DoThreadQueue(
  386. {$IFNDEF USE_OBJECT_ARC}
  387. InternalDoNotify
  388. {$ELSE}
  389. DoNotify
  390. {$ENDIF}
  391. , MainThreadUsesNotify
  392. );
  393. {$ELSE}
  394. if InMainThread then
  395. begin
  396. // RLebeau 9/4/2010: MainThreadUsesNotify only has meaning now when
  397. // TThread.Queue() is not available, as it calls the specified method
  398. // immediately if invoked in the main thread! To go back to the old
  399. // behavior, we would have to re-enable use of TIdNotifyThread, which is
  400. // another interface change...
  401. // RLebeau 6/21/2017: Delphi 10.2 Tokyo added TThread.ForceQueue() to let
  402. // the specified method be queued even if invoked by the main thread! So
  403. // lets re-enable use of TIdNotifyThread in earlier versions, to maintain
  404. // consistent notification behavior...
  405. if not MainThreadUsesNotify then
  406. begin
  407. {$IFNDEF USE_OBJECT_ARC}
  408. InternalDoNotify;
  409. {$ELSE}
  410. DoNotify;
  411. {$ENDIF}
  412. end else
  413. begin
  414. // TODO: if available, use TThread.CreateAnonymousThread() to call TThread.Queue()?
  415. //TThread.CreateAnonymousThread(Notify).Start;
  416. // TODO: FreePascal/Lazarus has Application.QueueAsyncCall(), but it is in the Forms unit!
  417. {
  418. uses Forms;
  419. procedure TIdNotify.NotifyAsync(Data: PtrInt);
  420. begin
  421. ($IFNDEF USE_OBJECT_ARC)
  422. InternalDoNotify;
  423. ($ELSE)
  424. DoNotify;
  425. ($ENDIF)
  426. end;
  427. Application.QueueAsyncCall(@NotifyAsync, 0);
  428. }
  429. {$IFNDEF USE_OBJECT_ARC}
  430. try
  431. {$ENDIF}
  432. CreateNotifyThread;
  433. GNotifyThread.AddNotification(Self);
  434. {$IFNDEF USE_OBJECT_ARC}
  435. except
  436. Free;
  437. raise;
  438. end;
  439. {$ENDIF}
  440. end;
  441. end else begin
  442. {$IFNDEF USE_OBJECT_ARC}
  443. try
  444. {$ENDIF}
  445. {$IFDEF HAS_STATIC_TThread_Queue}
  446. DoThreadQueue(
  447. {$IFNDEF USE_OBJECT_ARC}
  448. InternalDoNotify
  449. {$ELSE}
  450. DoNotify
  451. {$ENDIF}
  452. );
  453. {$ELSE}
  454. // TODO: FreePascal/Lazarus has Application.QueueAsyncCall(), but it is in the Forms unit!
  455. {
  456. uses Forms;
  457. procedure TIdNotify.NotifyAsync(Data: PtrInt);
  458. begin
  459. ($IFNDEF USE_OBJECT_ARC)
  460. InternalDoNotify;
  461. ($ELSE)
  462. DoNotify;
  463. ($ENDIF)
  464. end;
  465. Application.QueueAsyncCall(@NotifyAsync, 0);
  466. }
  467. CreateNotifyThread;
  468. GNotifyThread.AddNotification(Self);
  469. {$ENDIF}
  470. {$IFNDEF USE_OBJECT_ARC}
  471. except
  472. Free;
  473. raise;
  474. end;
  475. {$ENDIF}
  476. end;
  477. {$ENDIF}
  478. end;
  479. {$IFNDEF USE_OBJECT_ARC}
  480. procedure TIdNotify.InternalDoNotify;
  481. begin
  482. try
  483. DoNotify;
  484. finally
  485. Free;
  486. end;
  487. end;
  488. {$ENDIF}
  489. class procedure TIdNotify.NotifyMethod(AMethod: TThreadMethod; AForceQueue: Boolean = False);
  490. begin
  491. {$IFDEF HAS_STATIC_TThread_ForceQueue}
  492. DoThreadQueue(AMethod, AForceQueue);
  493. {$ELSE}
  494. if InMainThread then begin
  495. if not AForceQueue then begin
  496. AMethod;
  497. end else begin
  498. {$I IdSymbolDeprecatedOff.inc}
  499. with TIdNotifyMethod.Create(AMethod) do begin
  500. MainThreadUsesNotify := True;
  501. Notify;
  502. end;
  503. {$I IdSymbolDeprecatedOn.inc}
  504. end;
  505. end else begin
  506. {$IFDEF HAS_STATIC_TThread_Queue}
  507. DoThreadQueue(AMethod);
  508. {$ELSE}
  509. {$I IdSymbolDeprecatedOff.inc}
  510. TIdNotifyMethod.Create(AMethod).Notify;
  511. {$I IdSymbolDeprecatedOn.inc}
  512. {$ENDIF}
  513. end;
  514. {$ENDIF}
  515. end;
  516. {$IFNDEF HAS_STATIC_TThread_Queue}
  517. // RLebeau: this method does not make sense. The Self pointer is not
  518. // guaranteed to remain valid while this method is running since the
  519. // notify thread frees the object. Also, this makes the calling thread
  520. // block, so TIdSync should be used instead...
  521. {$I IdDeprecatedImplBugOff.inc}
  522. procedure TIdNotify.WaitFor;
  523. {$I IdDeprecatedImplBugOn.inc}
  524. var
  525. LNotifyIndex: Integer;
  526. LList: TIdNotifyList;
  527. begin
  528. repeat
  529. LList := GNotifyThread.FNotifications.LockList;
  530. try
  531. LNotifyIndex := LList.IndexOf(Self);
  532. finally
  533. GNotifyThread.FNotifications.UnlockList;
  534. end;
  535. if LNotifyIndex = -1 then begin
  536. Break;
  537. end;
  538. IndySleep(10);
  539. until False;
  540. end;
  541. {$ENDIF}
  542. {$IFDEF NotifyThreadNeeded}
  543. { TIdNotifyThread }
  544. procedure TIdNotifyThread.AddNotification(ASync: TIdNotify);
  545. begin
  546. FNotifications.Add(ASync);
  547. FEvent.SetEvent;
  548. end;
  549. constructor TIdNotifyThread.Create;
  550. begin
  551. inherited Create(True, False, 'IdNotify'); {do not localize}
  552. FEvent := TIdLocalEvent.Create;
  553. FNotifications := TIdNotifyThreadList.Create;
  554. end;
  555. destructor TIdNotifyThread.Destroy;
  556. var
  557. {$IFNDEF USE_OBJECT_ARC}
  558. LNotify: TIdNotify;
  559. {$ENDIF}
  560. LList: TIdNotifyList;
  561. begin
  562. // Free remaining Notifications if there is something that is still in
  563. // the queue after thread was terminated
  564. LList := FNotifications.LockList;
  565. try
  566. {$IFDEF USE_OBJECT_ARC}
  567. LList.Clear; // Items are auto-freed
  568. {$ELSE}
  569. while LList.Count > 0 do begin
  570. LNotify := {$IFDEF HAS_GENERICS_TList}LList.Items[0]{$ELSE}TIdNotify(LList.Items[0]){$ENDIF};
  571. LList.Delete(0);
  572. LNotify.Free;
  573. end;
  574. {$ENDIF}
  575. finally
  576. FNotifications.UnlockList;
  577. end;
  578. FreeAndNil(FNotifications);
  579. FreeAndNil(FEvent);
  580. inherited Destroy;
  581. end;
  582. class procedure TIdNotifyThread.FreeThread;
  583. begin
  584. if GNotifyThread <> nil then begin
  585. GNotifyThread.Stop;
  586. GNotifyThread.FEvent.SetEvent;
  587. GNotifyThread.WaitFor;
  588. // Instead of FreeOnTerminate so we can set the reference to nil
  589. FreeAndNil(GNotifyThread);
  590. end;
  591. end;
  592. procedure TIdNotifyThread.Run;
  593. // NOTE: Be VERY careful with making changes to this proc. It is VERY delicate and the order
  594. // of execution is very important. Small changes can have drastic effects
  595. var
  596. LNotifications: TIdNotifyList;
  597. LNotify: TIdNotify;
  598. begin
  599. FEvent.WaitForEver;
  600. // TODO: If TThread.Queue() is available, just queue the entire
  601. // FNotifications list to the main thread and exit. No sense in
  602. // locking and unlocking the list on every notification since we
  603. // will not be waiting on them here.
  604. //
  605. // Unlocking and relocking the list should only be needed if we
  606. // have to resort to using TThread.Synchronize(), so we don't block
  607. // other threads from queuing new notifications while a notification
  608. // is running...
  609. {
  610. ($IFDEF Use_DoThreadQueue)
  611. if not Stopped then begin
  612. try
  613. LNotifications := FNotifications.LockList;
  614. try
  615. while (LNotifications.Count > 0) and (not Stopped) do
  616. begin
  617. LNotify := ($IFDEF HAS_GENERICS_TList)LNotifications.Items[0]($ELSE)TIdNotify(LNotifications.Items[0])($ENDIF);
  618. LNotifications.Delete(0);
  619. ($IFNDEF USE_OBJECT_ARC)
  620. try
  621. DoThreadQueue(LNotify.InternalDoNotify);
  622. except
  623. FreeAndNil(LNotify);
  624. raise;
  625. end;
  626. ($ELSE)
  627. try
  628. DoThreadQueue(LNotify.DoNotify);
  629. finally
  630. LNotify := nil;
  631. end;
  632. ($ENDIF)
  633. end;
  634. finally
  635. FNotifications.UnlockList;
  636. end;
  637. except // Catch all exceptions especially these which are raised during the application close
  638. end;
  639. end;
  640. ($ENDIF)
  641. }
  642. // If terminated while waiting on the event or during the loop
  643. while not Stopped do begin
  644. try
  645. LNotifications := FNotifications.LockList;
  646. try
  647. if LNotifications.Count = 0 then begin
  648. Break;
  649. end;
  650. LNotify := {$IFDEF HAS_GENERICS_TList}LNotifications.Items[0]{$ELSE}TIdNotify(LNotifications.Items[0]){$ENDIF};
  651. LNotifications.Delete(0);
  652. finally
  653. FNotifications.UnlockList;
  654. end;
  655. {$IFDEF USE_DoThreadQueue}
  656. {$IFNDEF USE_OBJECT_ARC}
  657. try
  658. DoThreadQueue(LNotify.InternalDoNotify);
  659. except
  660. FreeAndNil(LNotify);
  661. raise;
  662. end;
  663. {$ELSE}
  664. try
  665. DoThreadQueue(LNotify.DoNotify);
  666. finally
  667. LNotify := nil;
  668. end;
  669. {$ENDIF}
  670. {$ELSE}
  671. try
  672. DoThreadSync(
  673. {$IFNDEF HAS_STATIC_TThread_Synchronize}Self,{$ENDIF}
  674. LNotify.DoNotify);
  675. finally
  676. FreeAndNil(LNotify);
  677. end;
  678. {$ENDIF}
  679. except // Catch all exceptions especially these which are raised during the application close
  680. end;
  681. end;
  682. end;
  683. {$ENDIF} // NotifyThreadNeeded
  684. { TIdNotifyMethod }
  685. {$I IdDeprecatedImplBugOff.inc}
  686. constructor TIdNotifyMethod.Create(AMethod: TThreadMethod);
  687. {$I IdDeprecatedImplBugOn.inc}
  688. begin
  689. inherited Create;
  690. FMethod := AMethod;
  691. end;
  692. {$I IdDeprecatedImplBugOff.inc}
  693. procedure TIdNotifyMethod.DoNotify;
  694. {$I IdDeprecatedImplBugOn.inc}
  695. begin
  696. FMethod;
  697. end;
  698. {$IFDEF NotifyThreadNeeded}
  699. initialization
  700. //CreateNotifyThread; // created on demand
  701. finalization
  702. TIdNotifyThread.FreeThread;
  703. {$ENDIF}
  704. end.