I know this is very unusual but I'm a bit despaired.
A couple of years ago and came across a component called TMultiTasker. This
component was exact what I was looking for and I used it for my program. In
the past years this component became a central function in my software and I
can't without it anymore.
Right now I'm still using C++ Builder 5 but I have to change to new version
soon. Now I noticed that this component does not work in CBuilder 6 and
above. I can install it but when I compile and a program I get an "Access
violation..." error.
I don't think it is an C++ Builder issue because I found out that people
reported the same issue with Delphi 6 and above.
So I'm not a delphi expert and I looked over it but I didn't see what's
wrong. I also tried to covert it to a C++ component but it didn't go well
either. And, of course I tried to contact the author as well. But no luck.
So all my hope is on you guys. I'm willing to pay up to 100 bucks if anyone
could fix this component. Either as delphi or (better) C++ Builder component
for version 6 and above.
The component can be found on
http://www.delphi-files.com/download/vcl/system/threads/multitasker.zip.
It's freeware.
Thank you very much.
Rob
P.S. This is not a joke or spam. I'm serious.
I looked briefly at the code. I'm afraid that I'd pay $200 to not look at
it again. The sight of 'TThread.suspend' was enough. I strongly suggest
that you no longer use this package as-is, even if you can get it working on
D6/whatever. Even the author has obviously had problems with 'suspend',
'suspended' etc. and I'm not surprised.
Controlling thread operation with suspend/resume is hazard-ridden.
Can you explain in more detail what you use this component for? There may
be some alternative that is more likely to work reliably.
Rgds,
Martin
Hi Martin,
what a pity. I've never experienced any problems with the component.
My goal is the following:
I monitor multiple directories (up to 100) for file changes (I'm using the
FindFirstChangeNotification function). If a file has been changed I call the
component to do some stuff in a thread. Now it might be that 20 directories
has been changed at the same time and of course I don't want to execute the
thread 20 times at the same time. So with the component It limit the threads
to 3 at a time and if one thread has been terminated the next thread will be
started. It works like a pool or queue.
A function of this component which is very useful in this context is the
LockExecution function. As soon as a thread calls LockExecution it can be
sure to execute a piece of code without sharing or interruption by any other
thread. The other threads have to wait until the first call of
UnLockExecution. I.e. I use the function to write into a log file.
I hope it is clear what I'm driving at.
Thanks Martin. Any help is really appreciated.
Rob
> It works like a pool or queue.
Windows has built-in thread pooling support:
Thread Pooling
http://msdn.microsoft.com/en-us/library/ms686756(VS.85).aspx
QueueUserWorkItem()
http://msdn.microsoft.com/en-us/library/ms684957(vs.85).aspx
> A function of this component which is very useful in this context
> is the LockExecution function. As soon as a thread calls
> LockExecution it can be sure to execute a piece of code without
> sharing or interruption by any other thread. The other threads have
> to wait until the first call of UnLockExecution. I.e. I use the function
> to write into a log file.
It uses a normal critical section for that. You can use one manually just
as easily.
Gambit
Obviously, you have now <g>
Just a thought, do you have to support Wintendo? If not, could you use the
'ReadDirectoryChangesW' API? I ask this because FFCN/FNCN returns a handle
upon which thread/s can wait and, IIRC, the wait functions are limited to 63
handles. Does this mean that you need two threads to monitor 100 trees?
'ReadDirectoryChangesW' can run overlapped, so eliminating the need for more
than one thread to monitor any number of folders and also eliminating
polling. RDCW however, is not suppoted on the 16-bit OS - you need W2k
Prof, XP, Windows servers or better, (or, in the case of Vista, worse:).
> My goal is the following:
>
> I monitor multiple directories (up to 100) for file changes (I'm using the
> FindFirstChangeNotification function). If a file has been changed I call
> the component to do some stuff in a thread.
OK. Presumably a thread has to receive the notification of whatever
tree/file has changed and then do something with it?
Now it might be that 20 directories
> has been changed at the same time and of course I don't want to execute
> the thread 20 times at the same time.
So... you need to detect the changes to folders/trees and queue the changes
to a small number of threads. The threads do 'stuff' with the
folders/trees/files.
Is that right?
So with the component It limit the threads
> to 3 at a time and if one thread has been terminated the next thread will
> be started. It works like a pool or queue.
I confess I did not look too closely at the code - the sight of 'suspend'
was enough for me, (as you might have noticed:). If the component is
continually creating and terminating threads, then it's not as good as I
initially thought it was.
> A function of this component which is very useful in this context is the
> LockExecution function. As soon as a thread calls LockExecution it can be
> sure to execute a piece of code without sharing or interruption by any
> other thread. The other threads have to wait until the first call of
> UnLockExecution. I.e. I use the function to write into a log file.
Usually better to queue the log entries to one, dedicated 'logger' thread.
This is way easier to manage and the log-producer threads do not have tt
fight their way into a CS that is locked for extended periods during disk
writes/flushes/change log file/whatever. Also, if your work is based on a
class, the 'used' class can be queued to the logger thread and the logger
can call a method of it to get text to write.
>
> I hope it is clear what I'm driving at.
>
I don't need your 100 greenbacks, but if I can suggest an easier approach,
then I will.
I suspect that you may be better off queuing the work, rather than the
threads.
Rgds,
Martin
Ah. Okay. Good point. I'll come back to this after I have the thread thing
running. This is more important right now.
>
> So... you need to detect the changes to folders/trees and queue the changes
> to a small number of threads. The threads do 'stuff' with the
> folders/trees/files.
>
> Is that right?
Exactly. This is what I'm doing.
>
> Usually better to queue the log entries to one, dedicated 'logger' thread.
Good point but how can I queue this to a thread? This is one of my problem.
I don't know how I can do this.
>
> I don't need your 100 greenbacks, but if I can suggest an easier approach,
> then I will.
Cheers, I really appreciate your help.
>
> I suspect that you may be better off queuing the work, rather than the
> threads.
What did you mean by this sentence? Sorry I'm not a native speaker and I
don't get the sentence.
Hi Danny,
thanks for the link. I already checked and my quick look told me that this
wasn't what I was looking for. But maybe I have to take a deeper look.
Thanks.
Rob.
Look at 'How sequence treads for a TidHTTP web spider' in
b.p.d.internet.winsock - sounds like a similar task.
Continually creating/suspending/terminating threads is very inefficient.
That, plus it's usually messy, complex, difficult to get right and difficult
to debug. If your work class can be queued to a small pool of threads, it's
much easier to manage.
Rgds,
Martin
> Good point but how can I queue this to a thread? This is one of
> my problem. I don't know how I can do this.
Simply write a thread class that has a thread-safe stringlist. Other
threads can push messages into the list when needed, and the thread can loop
through the list regularly extracting the items and pushing them to the log.
For example:
type
TLogger = class(TThread)
private
Msgs: TStringList;
Lock: TCriticalSection;
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
procedure Add(const Msg: String);
end;
constructor TLogger.Create;
begin
Msgs := TStringList.Create;
Lock := TCriticalSection.Create;
inherited Create(False);
end;
destructor TLogger.Destroy;
begin
Msgs.Free;
Lock.Free;
inherited Destroy;
end;
procedure TLogger.Add(const Msg: String);
begin
Lock.Enter;
try
Msgs.Add(Msg);
finally
Lock.Leave;
end;
end;
procedure TLogger.Execute;
var
List: TStringList;
I: Integer;
begin
List := TStringList.Create;
try
while not Terminated do
begin
Lock.Enter;
try
List.Assign(Msgs);
Msgs.Clear;
finally
Lock.Leave;
end;
if List.Count > 0 then
begin
for I := 0 to List.Count-1 do
begin
// add List[I] to log as needed...
end;
end else
Sleep(10);
end;
finally
List.Free;
end;
end;
Then the rest of your code can do this when needed:
var
Logger: TLogger = nil;
//...
Logger.Add('some line');
//...
Logger.Add('another line');
//...
initialization
Logger := TLogger.Create;
finalization
Logger.Free;
Gambit
What do you mean by "regularly"? How do you signal the thread that there's
work to do?
> while not Terminated do
> begin
> if List.Count > 0 then
> begin
> for I := 0 to List.Count-1 do
> begin
> // add List[I] to log as needed...
> end;
> end else
> Sleep(10);
> end;
Do you really want to have a thread busy waiting the user's notebook while
running on battery, sucking up battery life, and preventing pages from being
swapped out? Do you really want to be accessing properties of a stringlist
list without locking it?
> What do you mean by "regularly"?
At regular/frequent intervals.
> How do you signal the thread that there's work to do?
It determines that on its own by actually looking in the list periodically
to see if there are items available. I demonstrated that in my earlier code
example.
If you really want to implement signalling, then add a TEvent into the code,
ie:
type
TLogger = class(TThread)
private
Msgs: TStringList;
Lock: TCriticalSection;
Signal: TEvent;
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
procedure Add(const Msg: String);
end;
constructor TLogger.Create;
begin
Msgs := TStringList.Create;
Lock := TCriticalSection.Create;
Signal := TEvent.Create(nil, True, False, '');
inherited Create(False);
end;
destructor TLogger.Destroy;
begin
Msgs.Free;
Lock.Free;
Signal.Free;
inherited Destroy;
end;
procedure TLogger.Add(const Msg: String);
begin
Lock.Enter;
try
Msgs.Add(Msg);
Signal.SetEvent;
finally
Lock.Leave;
end;
end;
procedure TLogger.Execute;
var
List: TStringList;
I: Integer;
begin
List := TStringList.Create;
try
while not Terminated do
begin
while Signal.WaitFor(1000) <> wrSignaled do
Continue;
Lock.Enter;
try
List.Assign(Msgs);
Msgs.Clear;
finally
Signal.Reset;
Lock.Leave;
end;
for I := 0 to List.Count-1 do
begin
// add List[I] to log as needed...
end;
List.Clear;
end;
finally
List.Free;
end;
end;
> Do you really want to be accessing properties of a stringlist
> list without locking it?
Look at my earlier code again. There was locking involved. You stripped
that out when quoting it.
Gambit
It looked to me as though the locking was only used for protecting the list
before adding or removing items; which i understand and was was just fluff
for the question i had: how to notify the thread that items have been added.
The reason i asked that is because the OP's thread pool library is no longer
maintained; which Martin theorized because of inherit design problems around
Suspend/Resume. So i was left to wonder how you'd solve the signalling
problem without using Suspend/Resume.