source: branches/2.20_branch/Components/GenericThread.pas

Last change on this file was 15, checked in by RBRi, 19 years ago

+ components stuff

  • Property svn:eol-style set to native
File size: 11.4 KB
Line 
1Unit GenericThread;
2
3Interface
4
5uses
6{$ifdef os2}
7 os2def,
8{$else}
9 Controls,
10{$endif}
11 SysUtils, Classes, Messages,
12 ACLUtility;
13
14// Generic Thread Manager
15// Simply place a TGenericThreadManager component on a form
16//
17// You can then call
18// StartJob( <function>, <parameterobject> );
19// This will run <function> in the generic thread;
20// You can pass parameters by passing an object reference
21// (which can be cast to a pointer or longint or any object you like)
22// The job function must return an object (nil if you don't
23// have any data to return). This is passed to OnJobComplete
24//
25// Note: the job function must be a method e.g. a function in a form.
26//
27// Remember: once you call StartJob, you have *two* functions in
28// your code running at once! You should not use shared code
29// or variables; instead, call SendData and UpdateProgress (below).
30//
31// Use IsRunning to see if something is running in the thread
32//
33// Use Stop to ask the current job to stop
34// - The job function you started, should check StopRequested
35// regularly to see if they should stop, and exit if so
36// ForceStop asks the other thread to stop, like Stop,
37// but if the thread doesn't stop in the specified number
38// of seconds, it kills the thread. This will not free up memory
39// that the job function might have allocated
40//
41// If a job is running when the applicaiton shuts down,
42// (specifically, when the GenericThreadManager component is destroyed)
43// ForceStop is called with a 1 second timeout to make sure
44// the thread doesn't keep the app hanging around.
45// If you want to allow the job more time to stop, call ForceStop yourself
46// in a form close or similar.
47//
48// Your code can pass information from the job function to the main ithread
49// by three events, which you can assign in the IDE:
50// OnJobComplete
51// This is called when a job function is finished.
52// Passes the result object that the job function returns.
53//
54// OnProgressUpdate:
55// This is called whenever the job function calls UpdateProgress.
56// It allows you to conveniently pass a string message, and
57// and number out of a total (e.g. %, and 100)
58//
59// OnDataFromThread:
60// This is a generalised way for the job function to send the main
61// thread any data it likes. The function should simply call SendData.
62// The first parameter is a string, the second is an object, which
63// can be cast to whatever you like. Pass nil if you just want to use the string.
64//
65// Normally, you would allocate an object in the job function,
66// and free it in the handler for OnDataFromThread. You should not pass
67// any object that the job function destroys, because it might be destroyed
68// before the OnDataFromThread handler is called.
69//
70// Implementation Notes
71// In ForceStop we are not processing PM messages, so deadlock will result
72// if any callbacks are Synchronize'd. All callbacks are now protected
73// by checking to see if stop has been requested. However it is still possible
74// to deadlock if the callback happens at just the same time.
75
76const
77 WM_GENERICTHREADEVENT = WM_USER + 9721;
78
79type
80 TThreadJobProcedure = function( Parameters: TObject ): TObject of object;
81 TJobCompleteEvent = procedure( Result: TObject ) of object;
82 TThreadDataEvent = procedure( S: string; d: TObject ) of object;
83
84type
85 TThreadIdentifier =
86{$ifdef os2}
87 TID;
88{$else}
89 THandle;
90{$endif}
91
92 TGenericThreadManager = class;
93
94 TGenericThread = class( TThread )
95 FThreadManager: TGenericThreadManager;
96 FJobProcedure: TThreadJobProcedure;
97 FParameters: TObject;
98 FResult: TObject;
99 constructor Create( JobProcedure: TThreadJobProcedure;
100 Parameters: TObject );
101 procedure Execute; override;
102 function GetThreadIdentifier: TThreadIdentifier;
103 property ThreadIdentifier: TThreadIdentifier read GetThreadIdentifier;
104 end;
105
106{ TCopyableObject = class( TObject )
107 procedure CreateCopy( Source: TCopyableObject ); virtual; abstract;
108 end;
109
110 TGenericThreadEventMessage = packed record
111 Msg: Cardinal;
112 ThreadID: TThreadIdentifier;
113 Data: TObject;
114 Result: Longint;
115 end;
116
117 TGenericThreadWindow = class( TWinControl )
118 procedure WMEvent( Var Message: TGenericThreadEventMessage );
119 message WM_GENERICTHREADEVENT;
120 end;}
121
122 TGenericThreadManager = class( TComponent )
123 protected
124 FThread: TGenericThread;
125 FStopRequested: boolean;
126
127 // Notifications
128 FOnJobComplete: TJobCompleteEvent;
129 FOnProgressUpdate: TProgressCallback;
130 FOnDataFromThread: TThreadDataEvent;
131
132 // Thread notification variables
133 FProgressN: longint;
134 FProgressOutOf: longint;
135 FProgressMessage: string;
136
137 FDataString: string;
138 FDataObject: TObject;
139
140 FException: Exception;
141
142 procedure OnThreadTerminate;
143 procedure DoProgressUpdate;
144 procedure DoSendData;
145 procedure DoUnhandledException;
146 public
147{$ifdef os2}
148 procedure SetupComponent; override;
149{$else}
150 constructor Create( Owner: TComponent ); override;
151{$endif}
152 destructor Destroy; override;
153
154 procedure StartJob( JobProcedure: TThreadJobProcedure;
155 Parameters: TObject );
156 function IsRunning: boolean;
157
158 // Requests that the current thread (if any) should stop.
159 procedure Stop;
160
161 // Stops the current thread, kills it if it doesn't stop by
162 // itself after TimeLimit
163 procedure ForceStop( TimeLimit: longint );
164
165 // Procedures running in the thread should check this regularly to see
166 // if they should stop;
167 function StopRequested: boolean;
168
169 // Sends a progress update to the main thread; OnProgressUpdate is fired
170 procedure UpdateProgress( n, outof: integer;
171 Message: string );
172 // Sends a string and object to the main thread; OnDataFromThread is fired
173 procedure SendData( s: string; d: TObject );
174
175{ // Sends a string and object to the main thread;
176 // returns immediately without waiting for the data to be processed.
177 // OnDataFromThread is fired
178 procedure PostData( s: string; d: TCopyableObject );}
179 published
180 property OnJobComplete: TJobCompleteEvent read FOnJobComplete write FOnJobComplete;
181 property OnProgressUpdate: TProgressCallback read FOnProgressUpdate write FOnProgressUpdate;
182 property OnDataFromThread: TThreadDataEvent read FOnDataFromThread write FOnDataFromThread;
183
184 end;
185
186{$ifdef win32}
187procedure Register;
188{$endif}
189
190Implementation
191
192uses
193 Forms,
194{$ifdef os2}
195 Os2Def, BseDos, BseErr;
196{$else}
197 Windows;
198{$endif}
199
200{$ifdef win32}
201type
202 APIRET = DWORD;
203{$endif}
204
205constructor TGenericThread.Create( JobProcedure: TThreadJobProcedure;
206 Parameters: TObject );
207begin
208 FJobProcedure := JobProcedure;
209 FParameters := Parameters;
210 inherited Create( true ); // create suspended
211end;
212
213procedure TGenericThread.Execute;
214begin
215 try
216 FResult := FJobProcedure( FParameters );
217 except
218 on E: Exception do
219 begin
220 FThreadManager.FException := E;
221 Synchronize( FThreadManager.DoUnhandledException );
222 end;
223 end;
224
225 if FThreadManager.FStopRequested then
226 // to prevent deadlock when stopping, dont try and call back (could be in forcestop)
227 FThreadManager.FThread := nil // so threadmanager knows job is done
228 else
229 Synchronize( FThreadManager.OnThreadTerminate );
230
231end;
232
233function TGenericThread.GetThreadIdentifier: TThreadIdentifier;
234begin
235{$ifdef os2}
236 Result := ThreadID;
237{$else}
238 Result := Handle;
239{$endif}
240end;
241
242{// Generic Thread Window ------------------------
243procedure TGenericThreadWindow.WMEvent( Var Message: TGenericThreadEventMessage );
244begin
245end;
246}
247// Thread Manager ------------------------
248
249{$ifdef os2}
250procedure TGenericThreadManager.SetupComponent;
251begin
252 inherited SetupComponent;
253{$else}
254constructor TGenericThreadManager.Create( Owner: TComponent );
255begin
256 inherited Create( Owner );
257{$endif}
258
259 FThread := nil;
260 FStopRequested := false;
261
262 FOnJobComplete := nil;
263 FOnProgressUpdate := nil;
264 FOnDataFromThread := nil;
265end;
266
267destructor TGenericThreadManager.Destroy;
268begin
269 inherited Destroy;
270 if IsRunning then
271 ForceStop( 1 );
272end;
273
274procedure TGenericThreadManager.StartJob( JobProcedure: TThreadJobProcedure;
275 Parameters: TObject );
276begin
277 if FThread <> nil then
278 exit;
279 FStopRequested := false;
280 FThread := TGenericThread.Create( JobProcedure, Parameters );
281 FThread.OnTerminate := nil; // OnThreadTerminate; deadlockable
282 FThread.FreeOnTerminate := true;
283 FThread.FThreadManager := self;
284 FThread.Resume;
285end;
286
287procedure TGenericThreadManager.OnThreadTerminate;
288begin
289 if Assigned( FOnJobComplete ) then
290 FOnJobComplete( FThread.FResult );
291 FThread := nil;
292end;
293
294function TGenericThreadManager.IsRunning: boolean;
295begin
296 Result := FThread <> nil;
297end;
298
299procedure TGenericThreadManager.Stop;
300begin
301 if IsRunning then
302 FStopRequested := true;
303end;
304
305procedure TGenericThreadManager.ForceStop( TimeLimit: longint );
306var
307 ThreadID: TThreadIdentifier;
308 rc: APIRET;
309 TimeoutCount: longint;
310begin
311 if not IsRunning then
312 exit;
313 Stop;
314 ThreadID := FThread.ThreadIdentifier;
315
316 for TimeoutCount := 0 to TimeLimit * 10 do
317 begin
318{$ifdef os2}
319 rc := DosWaitThread( ThreadID, DCWW_NOWAIT );
320 if ( rc = 0 ) then
321 exit; // thread has finished
322 if ( rc <> ERROR_THREAD_NOT_TERMINATED ) then
323 exit; // some other error
324 // thread is still running
325 DosSleep( 100 );
326 end;
327 // Thread still running after timeout, kill it
328 DosKillThread( ThreadID );
329{$else}
330 rc := WaitForSingleObject( ThreadID, 0 );
331 if ( rc = WAIT_OBJECT_0 ) then
332 exit; // thread has finished
333 if ( rc <> WAIT_TIMEOUT ) then
334 exit; // some other error
335 // thread is still running
336 Sleep( 100 );
337 end;
338 // Thread still running after timeout, kill it
339 TerminateThread( ThreadID, 1 );
340{$endif}
341end;
342
343function TGenericThreadManager.StopRequested: boolean;
344begin
345 if FThread = nil then
346 begin
347 result := true; // shouldn't be here!
348 exit;
349 end;
350 result := FStopRequested;
351
352end;
353
354procedure TGenericThreadManager.UpdateProgress( n, outof: integer;
355 Message: string );
356begin
357 if FThread = nil then
358 exit;
359 if FStopRequested then
360 // to prevent deadlock when stopping
361 exit;
362 FProgressN := N;
363 FProgressOutOf := OutOf;
364 FProgressMessage := Message;
365 FThread.Synchronize( DoProgressUpdate );
366end;
367
368procedure TGenericThreadManager.DoProgressUpdate;
369begin
370 if not Assigned( FOnProgressUpdate ) then
371 exit;
372
373 FOnProgressUpdate( FProgressN,
374 FProgressOutOf,
375 FProgressMessage );
376end;
377
378procedure TGenericThreadManager.SendData( s: string; d: TObject );
379begin
380 if FThread = nil then
381 exit;
382 if FStopRequested then
383 // to prevent deadlock when stopping
384 exit;
385 FDataString := S;
386 FDataObject := d;
387 FThread.Synchronize( DoSendData );
388end;
389
390procedure TGenericThreadManager.DoSendData;
391begin
392 if not Assigned( FOnDataFromThread ) then
393 exit;
394
395 FOnDataFromThread( FDataString,
396 FDataObject );
397end;
398
399procedure TGenericThreadManager.DoUnhandledException;
400begin
401 Application.OnException( self, FException );
402end;
403
404{procedure TGenericThreadManager.PostData( s: string; d: TCopyableObject );
405begin
406 if not ( Owner is TForm ) then
407 raise Exception.Create( 'TGenericThreadManager.PostData: '
408 + 'Owner is not form, cannot post' );
409end;
410}
411
412{$ifdef win32}
413procedure Register;
414begin
415 RegisterComponents( 'ACL', [TGenericThreadManager] );
416end;
417{$endif}
418
419End.
Note: See TracBrowser for help on using the repository browser.