In an application i would like to launch a given task when the user performs
an action. This task always takes about 1-5 seconds and "never" fails(e.g. i
do not need user interaction during this task). Today the users presses a
button and the program "locks" in the 1-5second timeframe it takes to the
task to complete.
I would like to optimize this so the task is executed as a new thread
instead of in the mainthread but i'm not sure if i'm doing it the right way.
My thought was to create a TThread descendent where i set
"FreeOnTerminate:=true". After the creation i do not want to keep track of
the thread and therefore do something like this pseudo-code:
var
MyThread: TMyThread;
begin
MyThread:=TMyThread.create(True);
MyThread.FreeOnTerminate:=true;
MyThread.LoadDataIntoTheThreadForProcessing;
MyThread.Resume;
end;
...and nothing more at all...is this ok practice? or should i make sure, in
the main thread, that the thread is correctly terminated...?
/Thomas
This is OK if you don't need to create such a thread that frequently.
Your only potential problem with it is that you could run into a
scenario where one of these background threads is still working away
when the user closes your program. That would cause the thread to be
killed by the OS when the process is dismantled. It will get no chance
to do any cleanup or controlled exit. If it works with external
resources (like writing to a database index file) that might result in
corrupted data. If this is no problem for your kind of task you have a
workable and simple solution. If it is a problem you need to keep track
of the threads started this way and also which of them have died
already. In this case it may be simpler to change the design: create a
single worker thread at program start (or when you first need it) that
waits on a queue to which you can add an object that does the actual
work and has the data needed to do the job. Adding an object would wake
the thread (it would wait on an event object), it would process the
task and go back to a wait state when it finds the queue empty. This
way you can easily check the threads state and wait for it to finish
any ongoing task before you let the program end. You should always wait
with a timeout, though, in case a task gets jammed and blocks the
thread indefinitely.
I really should take the time to write some docs for my asynchronous
processing framework and post it to CodeCentral. If only the week had 8
days <g>...
--
Peter Below (TeamB)
Don't be a vampire (http://slash7.com/pages/vampires),
use the newsgroup archives :
http://www.tamaracka.com/search.htm
http://groups.google.com
But, sometimes it's easier to just throw some code together :-)
Here's a unit that I wrote a while back to do exactly what you are asking for
(I got the idea from the way Java does threads):
=== Usage ===
type
TMainForm = class(TForm)
private
FFileList: TObjectList;
public
procedure DoSomething(Arg: TObject);
procedure StartThread;
end;
procedure TMainForm.DoSomething(Arg: TObject);
begin
// This method will be executed in a separate thread //
end;
procedure TMainForm.StartThread;
begin
// Start thread with default argument
TThreadMethod.Create(DoSomething);
// Start another thread with a specific argument
TThreadMethod.Create(DoSomething, FFileList);
end;
=== Utility Unit ===
unit Util_ThreadMethod;
interface
uses
Classes;
type
TThreadMethod = class(TThread)
private
FMethodProc: TNotifyEvent;
FMethodArg: TObject;
public
constructor Create(AMethodProc: TNotifyEvent; AMethodArg: TObject = NIL);
procedure Execute; override;
end;
implementation
constructor TThreadMethod.Create(AMethodProc: TNotifyEvent; AMethodArg: TObject);
begin
inherited Create(FALSE);
FMethodProc := AMethodProc;
FMethodArg := AMethodArg;
FreeOnTerminate := TRUE;
end;
procedure TThreadMethod.Execute;
begin
FMethodProc(FMethodArg);
end;
end.
"Thomas Eg Jørgensen" <tho...@killspam.notaplan.com> wrote in message
news:486c...@newsgroups.borland.com...
Hello Peter,
I've been writing a thread lib too, some days ago, opensource.
Can I see what you have done?
Anyway, I named my lib as ParallelJobs, and i'll keep myself implement
some usefull things on it:
http://devpartners.projects.pro.br/forum/index.php?topic=19.0
For SVN and TRAC. The forum its for feedbacks or suggestions or even
some criticisms. ^^
We can share knowledge to create better libs ^^
--
[]'s
Gilberto Saraiva
http://gsaraiva.projects.pro.br/
I have thrown a ZIP archive into the borland.public.attachments group,
look for a post with caption asynctasks. The unit to look at is
primarily AsyncTasksU.pas. There is actually an older approach to
handling threading in the archive as well since it is used by the
logmanager stuff. I have not gotten around to reimplementing that with
the new framework yet.
> I have thrown a ZIP archive into the borland.public.attachments group,
> look for a post with caption asynctasks. The unit to look at is
> primarily AsyncTasksU.pas. There is actually an older approach to
> handling threading in the archive as well since it is used by the
> logmanager stuff. I have not gotten around to reimplementing that with
> the new framework yet.
Peter, thanks for sharing, I'll take a GOOD look on it ^^
only for preview experience, any demo already done?
Btw, the first impression of your lib are great, very good commented ^^
mine in comparison with your is as a black hole rsrrsrsrs
I did the same and put ParallelJobs on the attachments ^^
> I have thrown a ZIP archive into the borland.public.attachments group,
> look for a post with caption asynctasks. The unit to look at is
> primarily AsyncTasksU.pas. There is actually an older approach to
> handling threading in the archive as well since it is used by the
> logmanager stuff. I have not gotten around to reimplementing that with
> the new framework yet.
Hello Peter,
I can't "feedbacking" now cuz I still waiting for the demo :D
but, are there some special reason about the use of Interfaces
structure?
Have you see ParallelJobs?
> Peter, thanks for sharing, I'll take a GOOD look on it ^^
> only for preview experience, any demo already done?
Nope, zero. I am using it in some projects of mine but those cannot be
shared.
> I can't "feedbacking" now cuz I still waiting for the demo :D
None forthcoming in the forseeable future, sorry.
>
> but, are there some special reason about the use of Interfaces
> structure?
Makes it easier to do generic designs in my experience, and takes care
of lifetime control issues as well since I use reference-counted
interfaces.
> Have you see ParallelJobs?
Not yet and will not find the time for that until the weekend. The days
are too short and due to the exceedingly inefficient design of the
human body I even need to waste some of that on unproductive tasks like
sleeping <g>
> None forthcoming in the forseeable future, sorry.
ok, one day will have one
> Makes it easier to do generic designs in my experience, and takes care
> of lifetime control issues as well since I use reference-counted
> interfaces.
Uhm... so you're writing it to provide a good thread base for
extensions?
> Not yet and will not find the time for that until the weekend. The
> days are too short and due to the exceedingly inefficient design of
> the human body I even need to waste some of that on unproductive
> tasks like sleeping <g>
hehehehehe, who don't like to sleep? heheheheh unproductive but good ^^
ParallelJobs have a concept of provide parallel process, but not
provide a structured class for that, I still think to keep it on the
same course of simple win32 APIs.
Basicaly paralleljobs is able to do this:
{ TForm1 }
procedure TForm1.BackgroundUpdate;
begin
MessageBox(Handle, 'Test', 'test', MB_YESNO);
// a background update system...
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ParallelJob(Self, @TForm1.BackgroundUpdate);
MessageBox(Handle, 'Test', 'test', MB_YESNO);
end;
And the application still running normal, the two messages will be
displayed together.
> > Makes it easier to do generic designs in my experience, and takes
> > care of lifetime control issues as well since I use
> > reference-counted interfaces.
>
> Uhm... so you're writing it to provide a good thread base for
> extensions?
>
The AsynTasks unit is modeled on the .NET support for threading, it
basically allows you to execute any odd method or routine in the
context of a background thread. If the method takes parameters one has
to create a small helper object that implements the IExecutor interface
and which stores the parameters the thread needs to pass, but that is a
minor effort. Using an interface here instead of a base class allows
the programmer to implement the interface on any odd class (even a form
would do) instead of forcing him to derive from a specific base class.
Not counting the automatic lifetime management provided by Delphis
support for interfaces I generally find it easier to design with
interfaces than with classes: it forces you to completely concentrate
on the functionality you want to expose and not get lost in any details
on how to *implement* this functionality. And it promotes better data
hiding since interfaces simply have no way to directly expose data.
| The AsynTasks unit is modeled on the .NET support for threading, it
| basically allows you to execute any odd method or routine in the
| context of a background thread. If the method takes parameters one has
| to create a small helper object that implements the IExecutor
| interface and which stores the parameters the thread needs to pass,
| but that is a minor effort. Using an interface here instead of a base
| class allows the programmer to implement the interface on any odd
| class (even a form would do) instead of forcing him to derive from a
| specific base class.
The concept you're using is good ^^
We both are going on the way we like. I'm still far away from
dotNet programming.
In paralleljobs, parameters pass will be a long and endless fight,
rsrrsrsrsrs
but I'll code this piece with care... its not on project milestone yet,
but I'll add
it soon.
| Not counting the automatic lifetime management provided by Delphis
| support for interfaces I generally find it easier to design with
| interfaces than with classes: it forces you to completely concentrate
| on the functionality you want to expose and not get lost in any
| details on how to implement this functionality. And it promotes
| better data hiding since interfaces simply have no way to directly
| expose data.
Its not my fault, but I always try to avoid the use of Delphi units as
possible
as I can on projects like paralleljobs (low level libs)... cuz certain
types of
interactions need to be implemented before, for example, the SysUtils
initialization. I only keep it on mind when I'm write a lib for a
global
purpose. And who had already written a exception handler know what I
talking about. :D
And you still owes me a demo, put it on your check list. hehehehe only
joking
> ParallelJobs have a concept of provide parallel process, but not
> provide a structured class for that, I still think to keep it on the
> same course of simple win32 APIs.
OK, I have now found the time to look at the code. I hope you do not
take that personal, but this is a unit I would not use in my projects
even if i'm payed for it <g>. It is plain dangerous and also contains a
serious bug. If it worked in your tests it did so just by accident.
Let me enumerate the parts I do not like:
1. Due to (unnecessary) use of plain untyped pointers in the
ParallelJobs functions these are completely type-unsafe. You can pass
any odd address to them without the compiler slapping your hand, and
produce wonderful program meltdowns this way. And as said above this is
completely unnecessary: just define procedural types for the functions
you support and overloads of ParallelJobs that use these types:
type
TSimpleProc = procedure;
TSimpleMethod = procedure of object;
TSingleParamProc = procedure (param: pointer);
TSingleParamMethod = procedure (param: pointer) of object;
procedure ParallelJob( aSimpleProc: TSimpleProc); overload;
procedure ParallelJob( aSingleParamProc: TSingleParamProc; aParam:
Pointer); overload;
...
This also solves my second critique:
2. To implement the call to a method from the thread function you build
a call stub in memory obtained from the standard memory manager
(through a call to New). This memory will have read and write access
but not execute, so on any system with data execution prevention (DEP)
enabled the OS will terminate your process ruthlessly when it tries to
execute the call via the thunk.
If you store the properly typed procedural or method pointers in your
internal data structure you can use them for the call directly without
requiring a thunk (or assembler code).
3. You make no attempt to ensure that the memory manager is in a
thread-safe mode when your unit is used. Since you call
Windows.CreateThread directly the run-time library will not become
aware that the application is using secondary threads and will not set
the IsMultithread variable to true to make the memory manager
threadsafe. You should set it to true in the units initialization
section.
4. You are using SuspendThread/ResumeThread to control thread
execution. Never ever do that if you do not known *up front* that the
code the thread will execute will be in a safe state (not have any
locks on anything like the memory manager, for example) when you call
suspend. Otherwise you can deadlock your application with ease.
5. Your unit is way too free with its innards <g>. You expose too much
in the unit interface. All it should declare are the ParallelJobs
overloads. The data structures you use to implement all this stuff
should be private to the unit.
6. Oh yes, I mentioned a serious bug:
It is in your thread function:
function ParallelWorker(AParam: PParallelCall): Integer; stdcall;
asm
mov al, [ebx]
cmp al, 0
jz @SelfMode
jmp @Direct
You seem to have no clear grasp of how parameters are passed, depending
on calling convention. Your code assumes that the aparam pointer is
passed in ebx. That would be true if the function used register calling
convention (the Delphi default). But it uses stdcall (required by the
CreateThread API function), so the AParam pointer will be passed on the
stack, not in a register. If standard stack frames are enabled it would
sit at [ebp+4] if memory serves.
Back to the drawing board you go <g>....
> OK, I have now found the time to look at the code. I hope you do not
> take that personal,
I'll not, critics are very usefull, makes me conclude some obscures
points.
> but this is a unit I would not use in my projects
> even if i'm payed for it <g>. It is plain dangerous and also contains
> a serious bug. If it worked in your tests it did so just by accident.
I coded the main idea on a server application that use a lots of
sockets and needs to keep some buffers processing and calcs. I use D7
yet, don't know if is that what change some operation points on other
versions.
Anyway I've posted other demo on my site:
http://gsaraiva.projects.pro.br/downloads/?id=pjobs_ex1
I've made a simple synchronism with canvas lock ^^
Have you tried the use on another project? any errors?
let me know. ^^
>
> Let me enumerate the parts I do not like:
>
> 1. Due to (unnecessary) use of plain untyped pointers in the
> ParallelJobs functions these are completely type-unsafe. You can pass
> any odd address to them without the compiler slapping your hand, and
> produce wonderful program meltdowns this way. And as said above this
> is completely unnecessary: just define procedural types for the
> functions you support and overloads of ParallelJobs that use these
> types:
>
> type
> TSimpleProc = procedure;
> TSimpleMethod = procedure of object;
> TSingleParamProc = procedure (param: pointer);
> TSingleParamMethod = procedure (param: pointer) of object;
>
> procedure ParallelJob( aSimpleProc: TSimpleProc); overload;
> procedure ParallelJob( aSingleParamProc: TSingleParamProc; aParam:
> Pointer); overload;
> ...
I really like programmers that know what are programming, so that
souldn't be a big problem. If you want a parallel process, why the hell
you will pass a integer pointer as a callback function? :)
The untyped pointer is for future improviments on the parameters
system. I'll not keep my eyes on what param can be passed to the
callback, the programmer will create a function with his needs and i'll
provide the better system to work with it without knowing the type.
> This also solves my second critique:
>
> 2. To implement the call to a method from the thread function you
> build a call stub in memory obtained from the standard memory manager
> (through a call to New). This memory will have read and write access
> but not execute, so on any system with data execution prevention (DEP)
> enabled the OS will terminate your process ruthlessly when it tries to
> execute the call via the thunk.
>
> If you store the properly typed procedural or method pointers in your
> internal data structure you can use them for the call directly without
> requiring a thunk (or assembler code).
Thanks for remember me ^^
and yes, this is a piece of paralleljobs I'll code later with
VirtualAlloc its not necessary now on the current stage.
> 3. You make no attempt to ensure that the memory manager is in a
> thread-safe mode when your unit is used. Since you call
> Windows.CreateThread directly the run-time library will not become
> aware that the application is using secondary threads and will not set
> the IsMultithread variable to true to make the memory manager
> threadsafe. You should set it to true in the units initialization
> section.
My mistake, thanks.
> 4. You are using SuspendThread/ResumeThread to control thread
> execution. Never ever do that if you do not known *up front* that the
> code the thread will execute will be in a safe state (not have any
> locks on anything like the memory manager, for example) when you call
> suspend. Otherwise you can deadlock your application with ease.
This piece its for jobs group control. Control of the jobs on a Group
are made by the programmer, I can't limitate the programmer and don't
offer him the suspend. Agree?
>
> 5. Your unit is way too free with its innards <g>. You expose too much
> in the unit interface. All it should declare are the ParallelJobs
> overloads. The data structures you use to implement all this stuff
> should be private to the unit.
The Thunk type will have use after I provide a thunk creation system on
the library, for the programmer use on others win32 APIs, like
SetTimer, SetWindowsHook or even CreateThread.
The others procedures and functions are to jobs control. I'll add soon
a synchronization structure with VCL for jobs.
>
> 6. Oh yes, I mentioned a serious bug:
^^
> It is in your thread function:
>
> function ParallelWorker(AParam: PParallelCall): Integer; stdcall;
> asm
> mov al, [ebx]
> cmp al, 0
> jz @SelfMode
> jmp @Direct
>
> You seem to have no clear grasp of how parameters are passed,
> depending on calling convention. Your code assumes that the aparam
> pointer is passed in ebx. That would be true if the function used
> register calling convention (the Delphi default). But it uses stdcall
> (required by the CreateThread API function), so the AParam pointer
> will be passed on the stack, not in a register. If standard stack
> frames are enabled it would sit at [ebp+4] if memory serves.
Let me see if I've understand:
stdcall right->left call, ok?
but ebx will not be the first param?
| eax, edx, ecx, ebx |
take in consideration that I'm using D7, and stack frame on or off,
will not affect parraleljobs, I don't know about the running on other
delphis version. But I can take a look if necessary.
> Back to the drawing board you go <g>....
I'll wait for your anwser for that.
and thanks again ^^
very nice ^^
> I coded the main idea on a server application that use a lots of
> sockets and needs to keep some buffers processing and calcs. I use D7
> yet, don't know if is that what change some operation points on other
> versions.
No, the relevant stuff has not changed since D2.
> Have you tried the use on another project? any errors?
> let me know. ^^
No, sorry. You have to debug your stuff yourself, I have enough
problems of my own <g>.
>
> I really like programmers that know what are programming, so that
> souldn't be a big problem. If you want a parallel process, why the
> hell you will pass a integer pointer as a callback function? :)
That's no stance to take if you want to write library code for others
to use (or even yourself to use 2 years from now when you have
forgotten all the grimy details). Only amateurs leave armed bear traps
lying around for others to step into, if it can be easily avoided. Ever
heard of defensive programming? Very important for library code...
> > 4. You are using SuspendThread/ResumeThread to control thread
> > execution. Never ever do that if you do not known *up front* that
> > the code the thread will execute will be in a safe state (not have
> > any locks on anything like the memory manager, for example) when
> > you call suspend. Otherwise you can deadlock your application with
> > ease.
>
> This piece its for jobs group control. Control of the jobs on a Group
> are made by the programmer, I can't limitate the programmer and don't
> offer him the suspend. Agree?
No, definitely not. Defensive programming, see above.
>
> Let me see if I've understand:
> stdcall right->left call, ok?
> but ebx will not be the first param?
stdcall passes *all* parameters on the stack, the content of the
registers is undefined (formally at least). Your code seems to work
since the correct value happens to be in ebx by accident, but there is
no guarantee that it will always be there. You would be foolish to
depend on ebx to contain the correct value.
> | eax, edx, ecx, ebx |
> take in consideration that I'm using D7, and stack frame on or off,
> will not affect parraleljobs
Yes, I see you have a {$W+} in there, as required if you use assembly
code to access parameters.
| No, the relevant stuff has not changed since D2.
thats good!
| No, sorry. You have to debug your stuff yourself, I have enough
| problems of my own <g>.
Isn't my intention force anyone to test it.
| That's no stance to take if you want to write library code for others
| to use (or even yourself to use 2 years from now when you have
| forgotten all the grimy details). Only amateurs leave armed bear traps
| lying around for others to step into, if it can be easily avoided.
| Ever heard of defensive programming? Very important for library
| code...
hehehehehe, even in a dangerous florest?
defensive programming? what is that? hehehe only joking!
sometimes we need to forget some concepts to create new ones.
| No, definitely not. Defensive programming, see above.
ParallelJobs works like win32 APIs.
Will be better use TFarProc then Pointer?
For me is the same thing.
| stdcall passes all parameters on the stack, the content of the
| registers is undefined (formally at least). Your code seems to work
| since the correct value happens to be in ebx by accident, but there is
| no guarantee that it will always be there. You would be foolish to
| depend on ebx to contain the correct value.
Not accidentaly, CreateThread do the work it self.
| Yes, I see you have a {$W+} in there, as required if you use assembly
| code to access parameters.
$W its for lock system ^^
> ParallelJobs works like win32 APIs.
But it should do better if it can do better, no?
> Will be better use TFarProc then Pointer?
> For me is the same thing.
It is the same, TFarProc is just an alias for Pointer.
Anyway, it's your code and your furneral <g>.
> But it should do better if it can do better, no?
yep... will be, I guess. ^^
> It is the same, TFarProc is just an alias for Pointer.
Almost all APIs and function that need a Callback use pointers to hold
the callback address, I only do the same ^^ its not a mistake use it to
a callback.
> Anyway, it's your code and your furneral <g>.
Thats true ^^
but anyone can feel free to comment and make critics or even defend
some view points like you did.
And thanks again for the time and the corrections ^^