This is an important issue and one that must be considered when multithreading. It starts off with this statement... not everything in your application is thread aware (or thread safe). Even today, the majority of the VCL isn't. If you aren't aware of this, it can cause headaches that can be a real problem to track down.
I found this out quite a while ago. I was tinkering around with a multithread graphics app. It was nothing fancy, but it used multiple threads to plot different sections of an image. It ran perfectly on my machine and seemed to complete its task slightly quicker than its single threaded counterpart. Great I thought. I showed it to a colleague and it produced some very interesting results. I ran it again on my machine... no problems. His machine. *boom*.
The only difference was that his machine was a dual CPU system so instead of running piecemeal one after the other (as they did on my machine), both threads were running simultaneously and thats when the problems started. Both threads were accessing a TCanvas simultaneously and it really didn't like it very much.
Fortunately though, mechanisms are provided to handle this. So lets take a look at the major player... 'synchronize'
'Synchronize' is used to run a method of a thread within the context of the main VCL thread of your application. So if you are going to manipulate the majority of the VCL object hierarchy, or you're not absolutely 100% certain that the objects you'll be working with are thread safe, you would be advised to use 'synchronize'. Although the Delphi help does cover this topic, its not totally explicit as to which components are and which aren't thread safe.
I normally play it safe which simply means that if I'm going to be working with VCL objects or other components provided by 3rd parties, I synchronize. By all means try without synchronize, but as I found, this problem only manifested itself when the application was running on a multicore machine (if you can try it out on your machine... you lucky devil

So how do we use 'synchronize'?
Its actually pretty straight forward, but it does have certain limitations. The main one being that you can only call procedures that don't take any parameters.
As an example, lets consider our job processing thread example. When the job completes, the callback is executed in the context of this thread. The thread itself doesn't have a clue what that call back is going to do.. it could be accessing sections of code that aren't thread aware and this could spell disaster. So lets rework the job processing thread to ensure that it plays nice.
Code:
interface uses classes, syncObj; type TMyJobResult = class(TObject); TMyJobCompletedCallback = procedure(sender:TObject;jobResult:TMyJobResult) of object; TMyJobDescription = class(TObject) protected fCallback : TMyJobCompletedCallback; public property callback:TMyJobCompletedCallback read fCallback write fCallback; end; TMyJobProcessor = class(TThread) protected fJobResult : TMyJobResult; fJob : TMyJobDescription; fJobListCS : TCriticalSection; fJobList : TList; public constructor create; destructor destroy; override; procedure execute; override; procedure doCallback; procedure stop; procedure addJob(aJob:TMyJobDescription); end; interface constructor TMyJobProcessor.create; begin inherited create(true); self.freeOnTerminate:=true; fJobListCS:=TCriticalSection.create; fJobList:=TList.create; end; destructor TMyJobProcessor.destroy; begin while (fJobList.count>0) do begin try TMyJobDescription(fJobList[0]).free; except; end; fJobList.delete(0); end; try fJobList.free; except end; try fJobListCS.free; except end; inherited; end; procedure TMyJobProcessor.doCallback; begin if (assigned(fJob.callback)) then begin try fJob.callback(self,fJobResult); except fJobResults.free; end; end; end; procedure TMyJobProcessor.addJob(aJob:TMyJobDescription) begin fJobListCS.acquire; try fJobList.add(aJob); finally fJobListCS.release; end; if (self.suspended) then self.resume; end; procedure TMyJobProcessor.stop; begin self.terminate; if (self.suspended) then self.resume; end; procedure TMyJobProcessor.execute; begin repeat if (fJobList.count>0) then begin // Get the next job from the list fJobListCS.acquire; try fJob:=TMyJobDescription(fJobList[0]); fJobList.delete(0); finally fJobListCS.release; end; // Create the results holding object fJobResult:=TMyJobResult.create; // Process the job here // put the results in fJobResult // Now, execute the callback with synchronize synchronize(doCallback); // Now free up the job description object try fJob.free; except end; end else begin if (not self.terminated) and (fJobList.count=0) then self.suspend; end; until (self.terminated); end;
Some objects provide their own means of handling multiple threads. TCanvas for example provides two methods. 'lock' and 'unlock'. So this is thread safe...
Code:
... fACanvas.lock; try // Manipulate the canvas here finally fACanvas.unlock; end; ...
vBulletin Message