Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

Problem With Code, Please Help


  • Please log in to reply
8 replies to this topic

#1 programmer8329

programmer8329

    CC Lurker

  • New Member
  • Pip
  • 5 posts
  • Programming Language:Delphi/Object Pascal, Visual Basic .NET, Pascal
  • Learning:Delphi/Object Pascal

Posted 27 June 2012 - 04:58 AM

I found this help file, everything seems to be simple.

Waiting for a task to be completed
Sometimes, you need to wait for a thread to finish some operation rather than
waiting for a particular thread to complete execution. To do this, use an event object.
Event objects (TEvent) should be created with global scope so that they can act like
signals that are visible to all threads.
When a thread completes an operation that other threads depend on, it calls
TEvent.SetEvent. SetEvent turns on the signal, so any other thread that checks will
know that the operation has completed. To turn off the signal, use the ResetEvent
method.
For example, consider a situation where you must wait for several threads to
complete their execution rather than a single thread. Because you don’t know which
thread will finish last, you can’t simply use the WaitFor method of one of the threads.
Instead, you can have each thread increment a counter when it is finished, and have
the last thread signal that they are all done by setting an event.


The following code shows the end of the OnTerminate event handler for all of the
threads that must complete. CounterGuard is a global critical section object that
prevents multiple threads from using the counter at the same time. Counter is a global
variable that counts the number of threads that have completed.

procedure TDataModule.TaskThreadTerminate(Sender: TObject);
begin
ƒ
CounterGuard.Acquire; { obtain a lock on the counter }
Dec(Counter); { decrement the global counter variable }
if Counter = 0 then
Event1.SetEvent; { signal if this is the last thread }
CounterGuard.Release; { release the lock on the counter }
ƒ
end;
The main thread initializes the Counter variable, launches the task threads, and waits
for the signal that they are all done by calling the WaitFor method. WaitFor waits for a
specified time period for the signal to be set, and returns one of the values from Table

The following shows how the main thread launches the task threads and then
resumes when they have all completed:

Event1.ResetEvent; { clear the event before launching the threads }
for i := 1 to Counter do
TaskThread.Create(False); { create and launch task threads }
if Event1.WaitFor(20000) <> wrSignaled then
raise Exception;
{ now continue with the main thread. All task threads have finished }



Note If you do not want to stop waiting for an event after a specified time period, pass the
WaitFor method a parameter value of INFINITE. Be careful when using INFINITE,
because your thread will hang if the anticipated signal is never received.






My code

unit Unit1;

interface

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

type
TForm1 = class(TForm)
Button1: TButton;
CheckBox1: TCheckBox;
CheckBox2: TCheckBox;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
procedure DoWork1(Sender: TObject);
procedure FormCreate(Sender: TObject);

private
{ Private declarations }
public
{ Public declarations }
end;
TMyThread1 = class(TThread)
private
{ Private declarations }
protected
procedure DoWork;
procedure Execute; override;
end;

TMyThread2 = class(TThread)
private
{ Private declarations }
protected
procedure DoWork;

procedure Execute; override;
end;

var
Form1: TForm1;
T1 : TMyThread1;
T2 : TMyThread2;
event:tevent;
Counter:integer;
CounterGuard:TCriticalSection;
implementation

{$R *.dfm}
procedure TMyThread1.Execute;
begin

Synchronize(DoWork);
end;

procedure TMyThread2.Execute;
begin
Synchronize(DoWork);
end;

procedure TMyThread1.DoWork;

begin
Form1.CheckBox1.Checked := false;

end;

procedure TMyThread2.DoWork;
begin
Form1.CheckBox1.Checked := false;

end;

procedure Tform1.DoWork1(Sender: TObject);
begin


CounterGuard.Acquire; { obtain a lock on the counter }
Dec(Counter); { decrement the global counter variable }
if Counter = 0 then
event.SetEvent; { signal if this is the last thread }
CounterGuard.Release; { release the lock on the counter }

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


Event.ResetEvent;
T2 := TMyThread2.Create(False);
T1 := TMyThread1.Create(False);
T2.OnTerminate:=DoWork1;
T1.OnTerminate:=DoWork1;



if event.WaitFor(infinite)=wrSignaled then
showmessage('done');


Button1.Caption := 'Stop';

end;

procedure TForm1.FormCreate(Sender: TObject);
begin
event:=tevent.Create(nil,true,true,'');

Counter:=2;
end;

initialization

CounterGuard:=Tcriticalsection.Create;
end.


But this code doesn’t work. Did I do something wrong. Please help. It doesn’t signal when all threads stop executing code.
  • 0

#2 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 27 June 2012 - 06:53 AM

Hello programmer8329, welcome to codecall!

First, please wrap any codes inside CODE tags. It will make them easier to read and to understand. To do that, select the source code and click button with icon: "<>" (of course without quotes).

The main reason of your problem is caused by the fact that OnTerminate event of a TThread will be called/executed in main thread context. In your code, after launching the threads, main thread immediately enter "pause" mode because of this line:

if event.WaitFor(infinite)=wrSignaled then

The "pause" mode will be resumed only after the event is set (since you had set the expire time to infinite - meaning never expired). Basically the "pause" mode is waiting for the OnTerminate handler (DoWork1), while DoWork1 can not be executed because the "pause" mode. A typical deadlock situation.

The only fix is to set the event in the thread context, not in the main thread context. So do the setting somewhere within the Execute method of the thread, but not wrapped in Synchronize. Synchronize method will execute method given to it in main thread context.

By the way, in your code you actually never done anything in separate thread other than the main one. Since the Execute methods just call other method by wrapping them with Synchronize.

--- edit

On second look, actually OnTerminate will never be called by the threads since they never got terminated. Their Execute will never be complete execution because they contain calls to Synchronize. Since Synchronize executes in main thread and the main thread is "paused", meaning the Execute methods will never return.
  • 0

#3 programmer8329

programmer8329

    CC Lurker

  • New Member
  • Pip
  • 5 posts
  • Programming Language:Delphi/Object Pascal, Visual Basic .NET, Pascal
  • Learning:Delphi/Object Pascal

Posted 27 June 2012 - 07:39 AM

Dowork1 is executed, I checked, you can also check by putting showmessage(") line in dowork1. For some reason waitfor does not recieve signal from event, though I know that event is set in dwork1, I also checked it.
  • 0

#4 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 27 June 2012 - 07:46 AM

Okay, DoWork1 might be able to be executed. But that's only because your threads Execute code is just one liner which immediately get back to main thread (thanks to Synchronize). However DoWork1 will be executed before this line (the one that put the main thread in "pause" mode):

if event.WaitFor(infinite)=wrSignaled then

You can check by putting ShowMessage with different message just before that line. And you'll see that ShowMessage you've put in DoWork1 will be called first.
  • 0

#5 programmer8329

programmer8329

    CC Lurker

  • New Member
  • Pip
  • 5 posts
  • Programming Language:Delphi/Object Pascal, Visual Basic .NET, Pascal
  • Learning:Delphi/Object Pascal

Posted 27 June 2012 - 07:54 AM

What should I do to solve this problem? Can you write this example properly?


  • 0

#6 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 27 June 2012 - 09:11 AM

You can pass a reference of Form1 to your threads. And call DoWork1 in their Execute method. In your case you want to do the call as the last line. Something like this:

procedure TThread1.Execute;
begin
  ... // do your thread stuff here
  Form1.DoWork1;
end;

Note:
It's safe to do that since codes inside DoWork1 is thread safe (due to TCriticalSection protection). But in other case (in similar situation), you must check if the codes is safe to be called from separate thread context. Because codes in visual controls usually are not thread-safe.
  • 0

#7 programmer8329

programmer8329

    CC Lurker

  • New Member
  • Pip
  • 5 posts
  • Programming Language:Delphi/Object Pascal, Visual Basic .NET, Pascal
  • Learning:Delphi/Object Pascal

Posted 27 June 2012 - 10:02 AM

It will not change anything because there were no problems with calling dowork1 from executes of tnreads. Threads were caling dowork1 correctly . There was problem with event. I was working with this example more than weak. I noticed that execution of main thread does not stop and main thread does not wait result of event from dowork1. And that is problem , waitfor is supposed to wait for result, but i noticed that main thread
reaches button caption stop,though it should wait for result. I did something wrong there.


If you put zero instead of infinite it will not wait result,though it should.

If you put infinite excution of main will stop
  • 0

#8 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 27 June 2012 - 10:15 AM

It will not change anything because there were no problems with calling dowork1 from executes of tnreads. Threads were caling dowork1 correctly . There was problem with event.

while it looks working correctly, but you forgot that they currently executed in the main thread context instead in TTread1 or TThread2. The context difference makes it a big deal.

I was working with this example more than weak. I noticed that execution of main thread does not stop and main thread does not wait result of event from dowork1. And that is problem , waitfor is supposed to wait for result, but i noticed that main thread
reaches button caption stop,though it should wait for result. I did something wrong there.


Remember that I told you that in the current state DoWork1 will be called before you are reaching the WaitFor code? Since in DoWork1 you set the event, making WaitFor is useless, since the event has just been set. Put the following line before the event.WaitFor call and your program will be hung due to wait indefinitely.
event.ResetEvent;

If you put zero instead of infinite it will not wait result,though it should.

It should still wait. But timeout of 0 millisecond is the same with no need to wait, right?

If you could wrap your codes in your first post into CODE tags, just like I have shown in my sample of codes, I will fix it into working one.


  • 0

#9 programmer8329

programmer8329

    CC Lurker

  • New Member
  • Pip
  • 5 posts
  • Programming Language:Delphi/Object Pascal, Visual Basic .NET, Pascal
  • Learning:Delphi/Object Pascal

Posted 27 June 2012 - 10:20 AM

I also noticed one interesting thing if you put another button and set event there waitfor works correctly,but for some reason it does not work when dealing with threads. As if program first work with waitfor and after that with event when dealing with threads, though it should do opposite , and that is problem.
  • 0




Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download