Solved

Pause- Resume thread

Posted on 2004-10-17
13
552 Views
Last Modified: 2010-04-05
I use this code for pausing and resuming thread:
{in Unit1}
var
  Form1: TForm1;
  th:Threade;
   da:boolean;
implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
da:=true;

end;

procedure TForm1.Button1Click(Sender: TObject);

begin
th:=Threade.Create(true);

th.Resume;
 
end;

procedure TForm1.Button2Click(Sender: TObject);
begin

da:=true;
th.resume;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
{this is for pause thread}
da:=false;
end;

{In unit2}
procedure Threade.Execute;
var
i:Integer;
begin
i:=0;
while 1>0 do
  begin
     inc(i);
    form1.label1.Caption:=inttostr(i);
 
   if not da then break;
  end;

  if not da then Suspend;

end;

----------
but when i press Button2 my thread terminate
How can i pause and resume my thread.


 
0
Comment
Question by:nikola_mk
  • 6
  • 4
  • 2
  • +1
13 Comments
 
LVL 7

Expert Comment

by:sftweng
ID: 12336407
unit uQ_21171833;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, StdCtrls, uMyThread;

type
  TForm1 = class(TForm)
    ButtonStart: TButton;
    ButtonSuspend: TButton;
    ButtonResume: TButton;
    ButtonTerminate: TButton;
    Label1: TLabel;
    procedure ButtonStartClick(Sender: TObject);
    procedure ButtonResumeClick(Sender: TObject);
    procedure ButtonSuspendClick(Sender: TObject);
    procedure ButtonTerminateClick(Sender: TObject);
  private
    { Private declarations }
    myThread : TMyThread;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ButtonStartClick(Sender: TObject);
begin
  myThread := TMyThread.Create(True);
  myThread.Resume;
end;

procedure TForm1.ButtonResumeClick(Sender: TObject);
begin
  myThread.Resume;
end;

procedure TForm1.ButtonSuspendClick(Sender: TObject);
begin
  myThread.Suspend;
end;

procedure TForm1.ButtonTerminateClick(Sender: TObject);
begin
  myThread.Terminate;
end;

end.
-----------------------------
unit uMyThread;

interface

uses
  Classes;

type
  TMyThread = class(TThread)
  private
    { Private declarations }
    i : Integer;
    procedure ShowI;
  protected
    procedure Execute; override;
  end;

implementation

uses uQ_21171833, SysUtils;

{ Important: Methods and properties of objects in VCL or CLX can only be used
  in a method called using Synchronize, for example,

      Synchronize(UpdateCaption);

  and UpdateCaption could look like,

    procedure TMyThread.UpdateCaption;
    begin
      Form1.Caption := 'Updated in a thread';
    end; }

{ TMyThread }
var
  i : Integer;

procedure TMyThread.Execute;
begin
  { Place thread code here }
  i := 0;
  while not Terminated do
  begin
    Inc(i);
    Synchronize(ShowI);
    Sleep(10);
  end {WHILE};
end;

procedure TMyThread.ShowI;
begin
  Form1.Label1.Caption := IntToStr(i);
end;

end.
0
 
LVL 7

Expert Comment

by:sftweng
ID: 12336429
Oops - bad practice:
{ TMyThread }
var
  i : Integer

Delete this unit-level var since "i" is contained within the TMyThread, not the unit.
0
 
LVL 6

Expert Comment

by:vadim_ti
ID: 12336551
little change to your execute procedure



In unit2}
procedure Threade.Execute;
var
i:Integer;
begin
i:=0;
while 1>0 do
  begin
     inc(i);
    form1.label1.Caption:=inttostr(i);
 
   if not da then Suspend;
   if terminated then
      break;
  end;
end;

0
 
LVL 7

Expert Comment

by:sftweng
ID: 12336989
vadim_ti and nikola_mk, please note very carefully - you MUST use Synchronize within the thread to access any VCL components (see my example). The call "form1.label1.Caption:=inttostr(i);" will cause trouble.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12337057
procedure Threade.Execute;
var i:Integer;
begin
  i:=0;
  while 1>0 do begin
    inc(i);
    form1.label1.Caption:=inttostr(i);  
    if not da then break;
  end;
  if not da then Suspend;
end;

There's a code formatter at http://www.dow.wau.nl/aew/DelForExp.html which is free and very useful. If you format your code a bit nicer, you would see that after you syspend your thread, you would not execute anything anymore. Thus if you click button2 it will resume the thread and the thread runs out of anything to do thus it ends nicely, just like you told it to do. :-)

I'm not going to comment on your bad code use since I assume you still have a lot to learn. However, a better solution would be this:

procedure Threade.Execute;
var i:Integer;
begin
  i:=0;
  repeat
    inc(i);
    form1.label1.Caption:=inttostr(i);  
  until terminated;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  {this is for pause thread}
  th.Suspend.
end;

The repeat-until terminated just increases readability. :-) Terminated is a local thread variable that is set when you call th.Terminate.
Drop that da variable.

Now, another thing... Your thread depends on the form variable, which is not safe practice. If the form gets closed before the thread gets notified about this, the thread will generate an access violation and crash silently. Then again, you won't notice it because the thread crashes SILENTLY. (There's no exception handler in the thread code, and the main thread won't handle exceptions from other threads.) So add a FormClose event and call this code:

TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  th.Terminate; // Tell the thread to stop.
  th.WaitFor(Infinity); // Wait until the thread has really stopped.
  th.Free;
end;

But since your code is freeing the thread now, you must make a minor change to the thread's constructor.

type
  Threade = class(TThread)
  private
  protected
    //blablabla
  public
    //blablabla
    constructor Create; // Don't override this since there's nothing in the parent to override!
  end;

constructor Threade.Create;
begin
  inherited Create(True); // Creates the thread in suspended mode.
  FreeOnTerminate := False; // Prevents the thread from freeing itself.
  // Perhaps a resume here if you can add all your initialization code here...
end;

Be aware that most global variables are NOT thread-safe, especially forms and other components. If you want to use global variables or shared resources, you have to learn to use critical sections. Other options that you can use are semaphores, mutexes and events. (Con't confuse Windows events with Delphi events though!) Working with threads is not easy and requires you to think far ahead to consider the possible error-situations you might encounter. Bugs in multi-threaded applications are quite hard to solve, though.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12337070
@sftweng, no, you DON'T have to use synchronise to access VCL components but it is better to use them, if you're about to change values in a component that might also be changed by other components. In above code, only a label caption is modified. No components are added or removed and the label caption isn't modified from multiple places. The call to change the caption won't cause any problems. I've used something similar quite often, in progress forms where a thread in the background had to change captions and a progress bar on a form. The reason for this is quite simple even, although you must have some deep knowledge about these components, though.
Whenever you change a caption of a component, the component will just send a SetText message to the control below it. This message is then handled in the main thread and processed.
Whenever you call Synchronise to update a caption, the thread sends a message to the main thread so it will execute the synchronise method. It then waits for the main thread to respond back and say it has processed the method. If, in this method all you do is change the caption, it will only result in yet another message being sent to the main thread. The result is still the same, although now you're delaying the thread because it has to wait for more messages to be processed.

As I said before, using threads means that you have to look far, far ahead... Sometimes you can gain a lot of performance while at other times you can avoid bugs if you plan your threads quite well...
0
Highfive Gives IT Their Time Back

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 7

Expert Comment

by:sftweng
ID: 12337288
Workshop_Alex, while it is possible to use mutual exclusion semaphores (e.g., TCriticalSection) in some cases,  falling into the habit of ignoring synchronization will lead to future problems if (when) more than one thread becomes involved.

So yes, it isn't truly a "MUST" in the strictest sense to use Synchronize in this case, but trying to work without it invites code that is not thread-safe.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12337409
@sftweng, of course making a habit of using synchronise is a good habit. But when you need to increase performance (which I often have to do) then the first thing to do is see if and where you can avoid adding these kinds of wait cycles. If you're just a beginner at this multi-threading stuff then yes, use it and use it as often as you can. Once you're more experienced, you'll notice that at quite a few moments you can actually avoid using synchronise and see performance increase to up to three times faster.
Thus, think far, far ahead when you're working with threads. Try to be as familiar as possible with the code you're creating and try to avoid the need of synchronisation since every synchronisation method will cause delays in your application that might be avoided by using a different approach.

I'm just trying to kill a myth here about that you always need to synchronise VCL components. Often you don't, if you know what you're doing! It's only when assignments might start to conflict with one another that you have to use synchronise methods. But better safe than sorry, if you're a beginner. Use it when you're unsure about your code.
0
 
LVL 7

Expert Comment

by:sftweng
ID: 12337570
Workshop_Alex, I think we're in general agreement and I do understand the underlying issues (I've been doing parallel processing since the early 1980's).

I will observe, however, that any code which fails does not perform as well as code which does what it is supposed to do, however much slower. Optimization should be done at a global level through good design rather than at a micro level, tweaking cycles out. Micro-optimization should be done only once a bottleneck has been identified or a design constraint is not being met.

A lot of debate over a 50-pointer! :-)
0
 
LVL 12

Expert Comment

by:Lee_Nover
ID: 12338299
I can't remember when was I last used Synchronize .. Events, CSs and Messages :)
anyway a realy big No-No that nobody noticed: suspending the thread from itself !!!
actually TThread.Destroy calls Terminate and WaitFor :)
0
 
LVL 7

Expert Comment

by:sftweng
ID: 12338439
Actually, Lee_Nover, I did notice it which is why I provided an entirely new implementation.
0
 
LVL 12

Expert Comment

by:Lee_Nover
ID: 12339570
ok :) but noone mentioned it :)
0
 
LVL 17

Accepted Solution

by:
Wim ten Brink earned 50 total points
ID: 12339652
@Lee_Nover, I noticed it! If you checked the samples I've added you'll notice that I moved it to the main form.
You're right about the Destroy method calling Terminate and WaitFor. However, in this case I preferred to show it in the code and it never hurts to call it yourself.

But okay, a more complete example:

constructor TThreade.Create;
begin
  inherited Create(True);
  FreeOnTerminate := False;
  InitializeCriticalSection(CS);
end;

destructor TThreade.Destroy;
begin
  DeleteCriticalSection(CS);
  inherited;
end;

procedure TThreade.Execute;
begin
  FCount := 0;
  repeat
    EnterCriticalSection(CS);
    if (FCount = High(FCount)) then begin
      FCount := 0;
    end
    else begin
      Inc(FCount);
    end;
    LeaveCriticalSection(CS);
    Sleep(0);
  until Terminated;
end;

function TThreade.GetCount: Integer;
begin
  EnterCriticalSection(CS);
  Result := FCount;
  LeaveCriticalSection(CS);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Threade.Resume;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Threade.Suspend;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Threade.Free;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Threade := TThreade.Create;
  Threade.Resume;
  Timer1.Enabled := True;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Label1.Caption := IntToStr(Threade.Count);
end;

In this code the thread itself contains a critical section and uses it to change the counter variable. (Which I renamed from I to FCount and also made available as a property.) I also included a check to prevent an overflow. The Get method will check it when getting the value and I could have added a set method that would also use a critical section when I change the value. But in this case I just used the critical section within the loop itself. The buttons itself will resume and suspend the thread but be aware that threads keep a reference counter! If you suspend twice, it will only restart after you've resumed it twice. If you resume twice fist, you need to suspend twice too, to stop it. (Or 3 times if the thread was already started before the first resume.)
The code shows a good technique to update a label without synchronising. The big advantage is that the label is updated a fixed amounts per second instead of after every change. Since the user can't read a label that fast, this is just a nicer solution. Set the timer on your form to an interval of 50 and the label is still updated fast enough without it having a big inpact on the other thread.
0

Featured Post

Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

Join & Write a Comment

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 tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
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 shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

705 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

20 Experts available now in Live!

Get 1:1 Help Now