Question

When working with TThread descendant I basically have a choice:

  • set FreeOnTerminate to true which deletes my TThread descendant object but does not set it to NULL
  • do it manually and go through all the mess of removing it myself

What I basically need is a way to determine if thread is running so I did the following:

//------------------------------------------------------------------------------
// Thread descendant
//------------------------------------------------------------------------------
class TMyThread : public TThread
    {
    private:   UnicodeString StatusLine;  // Used for Synchronize function

               void __fastcall UpdateGUI();
    protected: virtual void __fastcall Execute();
    public:                 __fastcall TMyThread();
    };
//------------------------------------------------------------------------------
TMyThread *MyThread;
//------------------------------------------------------------------------------

// Thread constructor
__fastcall TMyThread::TMyThread() : TThread(true)
{
FreeOnTerminate = false;
Priority        = tpNormal;
}

 // Synchronize function for Form1
void __fastcall TMyThread::UpdateGUI()
{
Form1->Label1 = StatusLine; 
}

// Execute code
void __fastcall TMyThread::Execute()
{
Sleep(2000);
StatusLine = "I am almost done!";
Synchronize(&UpdateGUI);
}

// Thread terminate, delete object, set to NULL
void __fastcall TForm1::ThreadTerminateIfDone(TMyThread *T)
{
if (T != NULL && WaitForSingleObject(reinterpret_cast<void*>(T->Handle),0) == WAIT_OBJECT_0)
    {
    T->Terminate();
    T->WaitFor();
    delete T;
    T = NULL;
    }
}

// And initialization part which needs to check if thread is already running
void __fastcall TForm1::StartOrRestartThread(TObject *Sender)
{
// Remove old thread if done
ThreadTerminateIfDone(MyThread);

// Check if thread is running - NULL = not running and terminated or uninitialized
if (MyThread == NULL)
    {
    MyThread = new TMyThread();
    MyThread->Start();
    }
else
    {
    Application->MessageBox(L"Thread is still running please wait!", L"Error", MB_OK);
    }
}

This code works as it is. My questions are:

  • is there a way to simplify this? I need to set MyThread to NULL after it is done so that the object is not around until next call to start/restart? This cannot be done with FreeOnTerminate set to true because it deletes object. I can only try to access object which then generates exception (which I can catch but it is dumb). I really just need to know if MyThread is done with execution before I initialize it or restart it.

  • can I restart thread without terminating it (I don't really need to remove object until program finishes) - if I start thread I get "cannot call start on a running or suspended thread" exception.

Was it helpful?

Solution

Your ThreadTerminateIfDone() function needs to take the thread pointer by reference, otherwise it will not be able to set pointer to NULL correctly:

void __fastcall TForm1::ThreadTerminateIfDone(TMyThread* &T)

With that said, you can eliminate ThreadTerminateIfDone() altogether if you use the thread's OnTerminate event to keep track of whether the thread is running. When FreeOnTerminate is set to true, OnTerminate is triggered before the thread is freed, eg:

class TMyThread : public TThread
{
private:
    String StatusLine;  // Used for Synchronize function
    void __fastcall UpdateGUI();
protected:
    virtual void __fastcall Execute();
public:
    __fastcall TMyThread();
};
//------------------------------------------------------------------------------
extern TMyThread *MyThread;
//------------------------------------------------------------------------------

.

TMyThread *MyThread = NULL;

__fastcall TMyThread::TMyThread()
    : TThread(true)
{
    FreeOnTerminate = true;
    Priority        = tpNormal;
}

void __fastcall TMyThread::UpdateGUI()
{
    Form1->Label1 = StatusLine; 
}

void __fastcall TMyThread::Execute()
{
    Sleep(2000);
    StatusLine = "I am almost done!";
    Synchronize(&UpdateGUI);
}

void __fastcall TForm1::StartOrRestartThread(TObject *Sender)
{
    if (MyThread == NULL)
    {
        MyThread = new TMyThread();
        MyThread->OnTerminate = ThreadTerminated;
        MyThread->Start();
    }
    else
    {
        Application->MessageBox(L"Thread is still running please wait!", L"Error", MB_OK);
    }
}

void __fastcall TForm1::ThreadTerminated(TObject *Sender)
{
    MyThread = NULL;
}

To answer your other question, if you want the thread to be restartable then you have to change the thread design a bit, eg:

class TMyThread : public TThread
{
private:
    String StatusLine;  // Used for Synchronize function
    TEvent *RestartEvent;
    void __fastcall UpdateGUI();
protected:
    virtual void __fastcall Execute();
public:
    bool Busy;
    __fastcall TMyThread();
    __fastcall ~TMyThread();
    void __fastcall Restart();
};
//------------------------------------------------------------------------------
extern TMyThread *MyThread;
//------------------------------------------------------------------------------

.

TMyThread *MyThread = NULL;

__fastcall TMyThread::TMyThread()
    : TThread(true)
{
    FreeOnTerminate = true;
    Priority        = tpNormal;
    RestartEvent    = new TEvent(nil, true, true, "");
}

__fastcall TMyThread::~TMyThread()
{
    delete RestartEvent;
}

void __fastcall TMyThread::UpdateGUI()
{
    Form1->Label1 = StatusLine; 
}

void __fastcall TMyThread::Execute()
{
    while (!Terminated)
    {
        if (RestartEvent.WaitFor(1000) == wrSignaled)
        {
            if (Terminated) return;

            RestartEvent.ResetEvent();
            Busy = true;

            StatusLine = "I am doing something!";
            Synchronize(&UpdateGUI);

            Sleep(2000);

            StatusLine = "I am almost done!";
            Synchronize(&UpdateGUI);

            Busy = false;
        }
    }
}

void __fastcall TForm1::StartOrRestartThread(TObject *Sender)
{
    if (MyThread == NULL)
    {
        MyThread = new TMyThread();
        MyThread->OnTerminate = ThreadTerminated;
        MyThread->Start();
    }
    else if (!MyThread->Busy)
    {
        MyThread->Restart();
    }
    else
    {
        Application->MessageBox(L"Thread is still running please wait!", L"Error", MB_OK);
    }
}

void __fastcall TForm1::ThreadTerminated(TObject *Sender)
{
    MyThread = NULL;
}

OTHER TIPS

Running a while loop in the thread, with some wait at the top to allow it to run once round when signaled, is so preferable to continually creating/terminating/destroying threads, and associated micro-management, that no other course is sane.

Create the thread, put in a while loop with a wait at the top, signal it to run and never terminate the thread at all unless absolutely forced to.

Calls of death:

TThread.WaitFor
TThread.Synchronize
TThread.Terminate

Try very hard to not use these, ever.

Simple example:

TmyThread=class(TThread);
private;
  mySimpleEvent:TSimpleEvent;
public
  constructor create;
  procedure go;
end;

constructor TmyThread.create;
begin
  inherited create(true);
  mySimpleEvent:=TSimpleEvent.Create;
  resume;
end;

procedure TmyThread.go;
begin
  mySimpleEvent.SetEvent;
end;

procedure TmyThread.Execute;
begin
  while mySimpleEvent.WaitFor(INFINITE) do
  begin
    mySimpleEvent.ResetEvent;
    //code to do your stuff
  end;
end;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top