2010/1/20 Brad Campbell <b...@wasp.net.au>: > Is QueueAsyncCall() threadsafe?
As far as I know it is not. > > Let's say I have a communications child thread and I want to trigger a call > back in the main GUI thread. Traditionally I'd do something ugly like have a > timer polling a variable used as a flag, or polling a RtlEvent on a short > timeout, but this seems like a much nicer solution. > You can call TThread.Synchronize(), your thread will be stopped until the GUI thread executes your function. Other solution is to create a separate thread for handling such GUI functions (take a look at attached source); this doesn't stop your child thread. Be careful however because in this case you don't control when your method is called (it may be after your thread is already finished). > Also, is there any form of mutex or lock I can use to protect access to a > linked list shared between threads? It would appear that RTLCriticalSection > might do what I want. Yes, it will do fine. The TCriticalSection is just a wrapper over RTLCriticalSection. -- cobines
unit uGuiMessageQueue; {$mode objfpc}{$H+} interface uses Classes, SysUtils, syncobjs; type TGuiMessageProc = procedure (Data: Pointer) of object; PMessageQueueItem = ^TMessageQueueItem; TMessageQueueItem = record Method: TGuiMessageProc; Data : Pointer; Next : PMessageQueueItem; end; TGuiMessageQueueThread = class(TThread) private FWakeThreadEvent: PRTLEvent; FMessageQueue: PMessageQueueItem; FMessageQueueLastItem: PMessageQueueItem; FMessageQueueLock: TCriticalSection; {en This method executes some queued functions. It is called from main thread through Synchronize. } procedure CallMethods; public constructor Create(CreateSuspended: Boolean = False); reintroduce; destructor Destroy; override; procedure Terminate; procedure Execute; override; {en @param(AllowDuplicates If @false then if the queue already has AMethod with AData parameter then it is not queued for a second time. If @true then the same methods with the same parameters are allowed to exists multiple times in the queue.) } procedure QueueMethod(AMethod: TGuiMessageProc; AData: Pointer; AllowDuplicates: Boolean = True); end; procedure InitializeGuiMessageQueue; procedure FinalizeGuiMessageQueue; var GuiMessageQueue: TGuiMessageQueueThread; implementation uses LCLProc, uExceptions; const // How many functions maximum to call per one Synchronize. MaxMessages = 10; constructor TGuiMessageQueueThread.Create(CreateSuspended: Boolean = False); begin FWakeThreadEvent := RTLEventCreate; FMessageQueue := nil; FMessageQueueLastItem := nil; FMessageQueueLock := TCriticalSection.Create; inherited Create(CreateSuspended, DefaultStackSize); FreeOnTerminate := True; end; destructor TGuiMessageQueueThread.Destroy; var item: PMessageQueueItem; begin // Make sure the thread is not running anymore. Terminate; FMessageQueueLock.Acquire; while Assigned(FMessageQueue) do begin item := FMessageQueue^.Next; Dispose(FMessageQueue); FMessageQueue := item; end; FMessageQueueLock.Release; RTLeventdestroy(FWakeThreadEvent); FreeAndNil(FMessageQueueLock); inherited Destroy; end; procedure TGuiMessageQueueThread.Terminate; begin inherited Terminate; // Wake after setting Terminate to True. RTLeventSetEvent(FWakeThreadEvent); end; procedure TGuiMessageQueueThread.Execute; begin while not Terminated do begin if Assigned(FMessageQueue) then // Call some methods. Synchronize(@CallMethods) else // Wait for messages. RTLeventWaitFor(FWakeThreadEvent); end; end; procedure TGuiMessageQueueThread.QueueMethod(AMethod: TGuiMessageProc; AData: Pointer; AllowDuplicates: Boolean = True); var item: PMessageQueueItem; begin FMessageQueueLock.Acquire; try if AllowDuplicates = False then begin // Search the queue for this method and parameter. item := FMessageQueue; while Assigned(item) do begin if (item^.Method = AMethod) and (item^.Data = AData) then Exit; item := item^.Next; end; end; New(item); item^.Method := AMethod; item^.Data := AData; item^.Next := nil; if not Assigned(FMessageQueue) then FMessageQueue := item else FMessageQueueLastItem^.Next := item; FMessageQueueLastItem := item; RTLeventSetEvent(FWakeThreadEvent); finally FMessageQueueLock.Release; end; end; procedure TGuiMessageQueueThread.CallMethods; var MessagesCount: Integer = MaxMessages; item: PMessageQueueItem; begin while Assigned(FMessageQueue) and (MessagesCount > 0) do begin try // Call method with parameter. FMessageQueue^.Method(FMessageQueue^.Data); except on Exception do begin WriteExceptionToErrorFile; DebugLn(ExceptionToString); ShowExceptionDialog; end; end; FMessageQueueLock.Acquire; try item := FMessageQueue^.Next; Dispose(FMessageQueue); FMessageQueue := item; // If queue is empty then reset wait event (must be done under lock). if not Assigned(FMessageQueue) then RTLeventResetEvent(FWakeThreadEvent); finally FMessageQueueLock.Release; end; Dec(MessagesCount, 1); end; end; // ---------------------------------------------------------------------------- procedure InitializeGuiMessageQueue; begin GuiMessageQueue := TGuiMessageQueueThread.Create(False); end; procedure FinalizeGuiMessageQueue; begin GuiMessageQueue.Terminate; WaitForThreadTerminate(GuiMessageQueue.ThreadID, 10000); // wait max 10 seconds end; initialization InitializeGuiMessageQueue; finalization FinalizeGuiMessageQueue; end.
-- _______________________________________________ Lazarus mailing list Lazarus@lists.lazarus.freepascal.org http://lists.lazarus.freepascal.org/mailman/listinfo/lazarus