Go Premium for a chance to win a PS4. Enter to Win

x
?
Solved

Wait for thread to finish ?

Posted on 2006-07-16
14
Medium Priority
?
6,002 Views
Last Modified: 2008-01-09
Can someone write me some examples how to make that only X numbers of threads are running at the same time. For example if i have code like this http://www.experts-exchange.com/Programming/Programming_Languages/Delphi/Q_21912741.html (accepted answer code). How can i limit that only X numbers of threads are running at the same time. In this code there is while loop which crates threads and i want to add future of max running threads at the same time.
0
Comment
Question by:65zgtre45rr
  • 6
  • 5
  • 3
14 Comments
 
LVL 26

Accepted Solution

by:
Russell Libby earned 500 total points
ID: 17119536

You can use a thread counter, and wait for the thread limit to be less that a specified max limit before spawing a new thread. Example based off the code I gve you.

Russell

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type
  TFormMailSender   = class(TForm)
    ...
  end;

type
  TThreadSender     =  class(TThread)
  private
     // Private declarations
     FForm:         TFormMailSender;
     FMailSend:     TStringList;
     FIDHTTP:       TIdHTTP;
     FMailRecv:     String;
     FRelayURL:     String;
     FMail:         String;
  protected
     // Protected declarations
     procedure      AfterSend;
     procedure      BeforeSend;
     procedure      Execute; override;
  public
     // Public declarations
     constructor    Create(Form: TFormMailSender; Mail: String);
  end;

const
  MaxThreads        =  5;

var
  FormMailSender:   TFormMailSender;
  ThreadCounter:    Integer = 0;

implementation
{$R *.DFM}

procedure TThreadSender.BeforeSend;
begin

  // Get relay url
  FRelayURL:=FForm.EditSmtpRelayUrl.Text;

  // Get post parameters
  FMailSend.Values['email']:=FMail;
  FMailSend.Values['html']:=FForm.MemoHtml.Text;
  FMailSend.Values['subject']:=FForm.EditSubject.Text;
  FMailSend.Values['from']:=FForm.EditFrom.Text;
  FMailSend.Values['fromname']:=FForm.EditFromName.Text;

end;

procedure TThreadSender.AfterSend;
begin

  // Call in the main thread
  FForm.Memo1.Text:=FMailRecv;

end;

procedure TThreadSender.Execute;
begin

  // Create http component for mailing
  FIDHTTP:=TIdHTTP.Create(nil);

  // Resource protection
  try
     // Create string list for sending
     FMailSend:=TStringList.Create;
     // Resource protection
     try
        // Get post parameters
        Synchronize(BeforeSend);
        // Perform the post
        FMailRecv:=FIDHTTP.Post(FRelayURL, FMailSend);
        // Set results
        Synchronize(AfterSend);
     finally
        // Free string list
        FMailSend.Free;
     end;
  finally
     // Free component
     FIDHTTP.Free;
     // Drop the thread counter
     InterlockedDecrement(ThreadCounter);
  end;

end;

constructor TThreadSender.Create(Form: TFormMailSender; Mail: String);
begin

  // Increase the thread counter
  InterlockedIncrement(ThreadCounter);

  // Set parameters
  FForm:=Form;
  FMail:=Mail;
  SetLength(FRelayURL, 0);

  // Perform inherited (don't suspend)
  inherited Create(False);

  // Set thread props
  FreeOnTerminate:=True;
  Priority:=tpLower;

end;

procedure TFormMailSender.Button1Click(Sender: TObject);
var  count:         Integer;
     dwThreads:     Integer;
     Mail:          String;
begin

  Count:=0;
  while (Count < MemoList.Lines.Count) do
  begin
     while True do
     begin
        try
           InterlockedIncrement(ThreadCounter)
        finally
           dwThreads:=InterlockedDecrement(ThreadCounter);
        end;
        if (dwThreads < MaxThreads) then
           break
        else
           Application.ProcessMessages;
     end;
     Mail:=MemoList.Lines.Strings[Count];
     TThreadSender.Create(Self, Mail);
     Inc(Count);
  end;

end;

end.
0
 
LVL 17

Assisted Solution

by:Wim ten Brink
Wim ten Brink earned 500 total points
ID: 17120124
The secure way is by using a semaphore, and I'll add a sample console application to show how it works. (Am using just the Windows API here, since it's not that complex...)

program ThreadPool;

{$APPTYPE CONSOLE}

uses
  Windows;

var
  // Semaphore, to keep the limit.
  Semaphore: THandle;
  // Critical section so threads don't disturb each other.
  Section: TRTLCriticalSection;

function SomeThread(Parameter: Pointer): Integer;
var
  I: Integer;
begin
  // Thread function must return result.
  Result := 0;
  // Wait for the semaphore, which limits us to 10 threads.
  if (WaitForSingleObject(Semaphore, INFINITE) = WAIT_OBJECT_0) then
  begin
    try
      for I := 1 to 5 do
      begin
        EnterCriticalSection(Section);
        try
          WriteLn('Thread ', Integer(Parameter), ': ', I: 2);
        finally LeaveCriticalSection(Section);
        end;
        Sleep(100);
      end;
      EnterCriticalSection(Section);
      try
        WriteLn('Thread ', Integer(Parameter), ' done');
      finally LeaveCriticalSection(Section);
      end;
    finally ReleaseSemaphore(Semaphore, 1, nil);
    end;
  end;
end;

const
  InitialCount = 10;
  MaximumCount = 10;
var
  Threads: array[1..50] of THandle;
  ThreadID: Cardinal;
  I: Integer;
begin
  // Semaphore for the limit of 10 threads.
  Semaphore := CreateSemaphore(nil, InitialCount, MaximumCount, nil);
  // Critical section for thread-0safe access to console.
  InitializeCriticalSection(Section);
  // Just starting 50 threads.
  for I := Low(Threads) to High(Threads) do
  begin
    Threads[I] := BeginThread(nil, 0, SomeThread, Pointer(I), 0, ThreadID);
    // Let the thread do some actions now.
    Sleep(0);
  end;
  // Now wait for all threads to finish.
  WaitForMultipleObjects(Length(Threads), @Threads, True, INFINITE);
  // End of critical section.
  DeleteCriticalSection(Section);
  // Close the semaphore.
  CloseHandle(Semaphore);
  // All done.
  WriteLn('All done.');
end.

Okay, some explanation. Since I wanted to keep this a short example, I used BeginThread for the thread and not a TThread class. When you're doing simple thread actions, this often tends to be easier, if you're familiar with the Windows API. But normally this code would be inside your Execute method of your own TThread class.

Since I'm generating output to the consolde I added a critical section for synchronisation purposes. So all WriteLn statements are protected by them. Synchronisation is always a bit troublesome with threads since you have to think them through thoroughly.

But the main task is done by the semaphore. The application starts by generating a semaphore. I want a maximum of 10 threads active at the same time, even though I have generated 50 threads! (The others will just sleep until the semaphore activates one of them again.) The InitialCount is there to tell the system how many available 'slots' there are. With a semaphore, you can release additional slots up to the maximum. (Maximum in this case is 10.)

Every thread will have to wait until the semaphore has a free slot for them. So in the beginning, 10 threads will almost immediately execute. The 11th thread will just have to wait until one of the other threads is finished. Once a thread releases the semaphore, a slot is available again which can trigger the next thread.

Of course there is no limitation in the numer of times a single thread can wait for a free slot and release it again, as long as you make sure the slots are ALWAYS released. (Hence the try-finally construction.)

But because this is a console application and the main thread will finish after starting all 50 threads, I decided that I would also want to wait for all threads to be finished. This you can do again simply by waiting for them. This time by waiting for multiple objects, which is why I need all thread handles in a single array. Btw, you would only have to wait for all threads to be finished only when you e.g. close your application.

This method is quite safe to use, especially since the semaphore will do all the counting checks for you. And basically it means you can start hundreds of threads at the same time and your system will still limit them to a limited number.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17120228
Additional note: If you add the following code to the thread function BEFORE the WaitForSingleObject line then you can see when the threads are created.

  EnterCriticalSection(Section);
  try
    WriteLn('Thread ', Integer(Parameter), ' created');
  finally LeaveCriticalSection(Section);
  end;

And when you use it, you will see that all threads are created almost immediately. So this solution will mean you have 50 threads at some point.

Also, after the line WaitForMultipleObjects you will need to add these lines:

  for I := Low(Threads) to High(Threads) do
    CloseHandle(Threads[I]);

Yes, I forgot to close all the threads... :-) For this example it's not important, though. And the TThread class will do this for you automatically.

Anyway, my approach will differ a bit from what you wanted because in my approach there is no limit to the number of created threads. It's just that most of them will be asleep and thus eat no processor time. (Just system resources.) Basically, the advantage of this is that you don't have to control the creation of the threads and put a limit there. You just put the limit in the threads themselves so your main thread doesn't have to be bothered about it.

Of course, what you could also do is wait for e.g. 5000 milliseconds instead of INFINITE. Then WaitForSingleObject will return the value WAIT_TIMEOUT instead and in my example this would mean the code inside the thread won't be executed. This would be a time-out implementation, again arranged entirely from inside the thread.

Also keep in mind that although I create the threads in a simple order, there is no way of telling in which order the threads will finish, even though all execute exactly the same code and thus should all take about the same amount of time.) The only guarantee you have is that 10 threads will be running, an d no more than 10. All other threads, while active, are all asleep. Threads that are asleep eat no CPU cycles. They are activated by the semaphore when a slot is released.
0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17120335
Finally, one minor warning... WaitForMultipleObjects cannot wait for more than MAXIMUM_WAIT_OBJECTS objects, which means that there's a maximum limit of 64 threads. If you have more than 64 threads then you will need this solution:

  // Now wait for all threads to finish.
  I := Low(Threads);
  while (I < High(Threads)) do
  begin
    Count := Length(Threads) + 1 - I;
    if (Count > MAXIMUM_WAIT_OBJECTS) then Count := MAXIMUM_WAIT_OBJECTS;
    WaitForMultipleObjects(Count, @Threads[I], True, INFINITE);
    I := I + Count;
  end;

You would also have to use this solution when you would be using a dynamic array for your threads. (And add a variable Count:Integer to your Var section.) Then again, you would only need this when you want to wait for all the threads to finish. And there are many other ways to do this waiting. It's just that I wanted to make this a better example.
0
 

Author Comment

by:65zgtre45rr
ID: 17151185
rllibby:
Your solutions looks nice and simple but i can't use Application.ProcessMessages becuse it freezes my form. I don't want that that is why i using threads. Can you make it on some other way.

Workshop_Alex:
It is a little complicated for me to rewrite your code for my code, so it would be nice if you could do it.
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 17151221

How do you plan on waiting then? Eg, you have 10 lines to process, you want 5 max threads, you spin off 5 threads to work on the first 5 lines, how do you intend to wait for one of the threads to finish so you process the remaining 5 lines?

Russell

0
 

Author Comment

by:65zgtre45rr
ID: 17153338
Is there any way that wan't freez the form ?
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 17153534

Application.ProcessMessages (which processes the message loop) should NOT freeze the form, just the opposite in fact. You probably have something else going on in your form (code wise), and its hard to say without seeing the code you currently have.

Russell
0
 

Author Comment

by:65zgtre45rr
ID: 17243487
I can't get this working. Meybe it would be uasier to write code that will do the folowing:

- create thread
- wait for this thread to finish
- create another thread

0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17248129
Application.ProcessMessages could freeze the mainform and whe whole application, btw. How? The dreaded deadlock situation! When you call Application.ProcessMessages then some other event might get triggered that is waiting for some flag that hasn't been set yet. Thus ProcessMessages keeps waiting for this event to finish before it sets the flag to say the event can finish. You see the impossibility in that?

Still, I myself would suggest that you use some threadpool in some way. Or else as you suggest yourself, start them one by one.
0
 

Author Comment

by:65zgtre45rr
ID: 17249782
Yes this is why i want to start 1 thread and wit for this thread to finish and then start another one. Can someone write me pice of code that will wait for 1 thread to finish and then start another one.

Thank you.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17250018
Actually, you don't want to wait for the thread to finish because while your are waiting, the thread that does the waiting will be frozen...
If you create a thread by inheriting from TThread then you have an OnTerminate event which will be called right before the thread finishes. You can use that event to trigger the next event.

Unfortunately, maintaining many threads, either one by one or as a pool, can become a complex experiment. You will have to keep track of lots of possible issues. We can write some code for you based on the information you have given us but that's no use because something else in your code might cause something that will make the system fall down again. It is more useful if you understand the way threads work and then create what you need yourself. Especially considering what you are trying to do here.
0
 

Author Comment

by:65zgtre45rr
ID: 17250489
I think that this OnTerminate event would be fine, could you write me something that would work with this. I think that it would look like this. OnTerminate event will set some golbal veribale to TRUE and in man form there will be code which will check for if this variable set to TRUE and then create new thread and set varibale back to false. I'm i right ? Can you write somthing like this please ?
0
 

Author Comment

by:65zgtre45rr
ID: 17262203
Can someone do it ?
0

Featured Post

Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Integration Management Part 2
Look below the covers at a subform control , and the form that is inside it. Explore properties and see how easy it is to aggregate, get statistics, and synchronize results for your data. A Microsoft Access subform is used to show relevant calcul…

916 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question