If you're brave, you can use a helper thread to make the whole operation thread-safe. That fails the "use as little extra code as possible" requirement, but is a surprisingly powerful technique and not as difficult as you might think.
Using synchronize methods in your worker threads can lead to deadlocks and often turns the resulting implementation into one that effectively just serializes everything and eliminates any benefits you might gain from using multiple threads in the first place. What you can do is create: TLoggerThread = class(TThread) and keep it inside the implementation section of your logging unit. Use the unit initialization to create it, and the finalization section of the unit to terminate it. You then get your WriteLog procedure to redirect to a matching procedure inside the TLoggerThread. So your WriteLog method becomes Procedure WriteLog(sString : string; opt : LogOption); Begin MyLoggerThread.WriteLog(sString,opt); End; Your TLoggerThread.WriteLog is only a little more complex: Procedure TLoggerThread.WriteLog(sString : string; opt : LogOption); Begin EnterCriticalSection(cs); { where cs is a critical section created and owned by the TLoggerThread } Try LogList.Add(TLogItem.create(sString,opt)); { where LogList is a list of TLogItems owned by the TLoggerThread, And a TLogItem is a class that has a string and an opt } Windows.SetEvent(WakeUp); { where WakeUp is a regular Windows Event object created and owned by the TLoggerThread } Finally LeaveCriticalSection(cs); End; End; This still doesn't get any logging done, but it means that your threads that call WriteLog don't wait on the application's main thread, nor on the LoggerThread. The info is just stashed away for subsequent processing. The fun part happens in your TLoggerThread.ExecuteMethod: Procedure TLoggerThread.Execute; Var Events : array[0..1] of THandle; Signal : integer; Begin Events[0] := DieEvent; { another regular windows event, Created and owned by the TLoggerThread } Events[1] := WakeUp; While not terminated do Begin Signal := Windows.WaitForMultipleObjects(2,@Events,false,INFINITE); If Signal = WAIT_ABANDONED then Begin Terminate; Exit; End; { This will happen if the application is being torn down as a result Of an end-task or similar } Signal := Signal - WAIT_OBJECT_0; { Signal should now be zero if the thread has been told to die Or 1 if it has been told to wake up } If Signal = 0 then Begin Terminate; Exit; End; { at this point, you probably have something to log } Synchronize(DoTheWork); { This method switches back to the application's main thread So it is safe to work with the VCL components, but you still Haven't blocked the callers to your WriteLog procedure. } End; End; Procedure TLoggerThread.DoTheWork; Var LogItem : TLogItem; Begin EnterCriticalSection(cs); Try While LogList.Count > 0 do Begin LogItem := TLogItem(LogList[0]); With frmMain.reLog do begin If lines.Count > 200 then lines.Delete(0); lines.add(LogItem.sString); SelStart := length(text) - (length(LogItem.sString)+2); SelLength := length(LogItem.sString); Case LogOption(LogItem.opt) of loStart : begin SelAttributes.Style := [fsbold]; SelAttributes.Color := clBlue; end; loNormal : begin SelAttributes.Style := []; SelAttributes.Color := clBlack; end; loError : begin SelAttributes.Style := [fsbold]; SelAttributes.Color := clRed; end; loFinished: begin SelAttributes.Style := [fsbold]; SelAttributes.Color := clBlue; lines.add(''); end; End; SelStart := Length(Text); Perform(EM_SCROLLCARET, 0, 0); end; LogItem.Free; LogList.Delete(0); End; Finally LeaveCriticalSection(cs); End; End; The last thing you need to do with your TLoggerThread is override the Terminate method. Procedure TLoggerThread.Terminate; Begin Windows.SetEvent(DieEvent); Inherited Terminate; End; There are a few other details, such as overriding the constructor of TThread in your TLoggerThread so that you create the Windows events and the list, and overriding the destructor to call closeHandle on the Windows events and to destroy the list, but the guts of it is there. Don't worry that the TThread.Terminate method is not virtual, as long as in your unit's finalization section you call the Terminate method of the TLoggerThread (and not the Terminate method of a TThread), it will work. Sorry, it is probably a lot of code to achieve what seems to be a simple task, but it should be pretty safe and avoid many of the pitfalls of having multiple threads trying to access the UI simultaneously. Trevor -----Original Message----- From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED] On Behalf Of Nick Sent: Thursday, 17 May 2007 9:08 a.m. To: NZ Borland Developers Group - Delphi List Subject: [DUG] Best way to make this thread safe I got a function here I use for logging to a richedit on my main form (this is just in functions unit) Procedure WriteLog(sString : string; opt : LogOption); Begin With frmMain.reLog do begin If lines.Count > 200 then lines.Delete(0); lines.add(sString); SelStart := length(text) - (length(sString)+2); SelLength := length(sString); Case LogOption(opt) of loStart : begin SelAttributes.Style := [fsbold]; SelAttributes.Color := clBlue; end; loNormal : begin SelAttributes.Style := []; SelAttributes.Color := clBlack; end; loError : begin SelAttributes.Style := [fsbold]; SelAttributes.Color := clRed; end; loFinished: begin SelAttributes.Style := [fsbold]; SelAttributes.Color := clBlue; lines.add(''); end; End; SelStart := Length(Text); Perform(EM_SCROLLCARET, 0, 0); end; End; Now I want to use that function in my threads to log things. However if I call it just like this WriteLog('Failed to connect! DB Error', loError); I could run into access violations But not quite sure the best way to do this to make it thread safe and use as little extra code as possible.. Should I create my own function like this WriteLogThread('Failed to connect! DB Error', loError); in my TThread and then in that procedure just do criticalsection start WriteLog(message, st); //this calls the function in my general functions unit criticalsection end or could I just use critical sections in my functions unit around the procedure. I hope that makes sense =) Thanks guys. _______________________________________________ NZ Borland Developers Group - Delphi mailing list Post: delphi@delphi.org.nz Admin: http://delphi.org.nz/mailman/listinfo/delphi Unsubscribe: send an email to [EMAIL PROTECTED] with Subject: unsubscribe _______________________________________________ NZ Borland Developers Group - Delphi mailing list Post: delphi@delphi.org.nz Admin: http://delphi.org.nz/mailman/listinfo/delphi Unsubscribe: send an email to [EMAIL PROTECTED] with Subject: unsubscribe