Solved

Wait for thread to finish ?

Posted on 2006-07-16
14
5,358 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 250 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 250 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
 
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
What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

 
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

Why You Should Analyze Threat Actor TTPs

After years of analyzing threat actor behavior, it’s become clear that at any given time there are specific tactics, techniques, and procedures (TTPs) that are particularly prevalent. By analyzing and understanding these TTPs, you can dramatically enhance your security program.

Join & Write a Comment

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…
This video explains how to create simple products associated to Magento configurable product and offers fast way of their generation with Store Manager for Magento tool.

758 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

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now