0

My problem is that I have instanciated two TThread (from Borland C++ VCL). Both of their constructors succeeds. But only the first TThread is executed.

(The goal of this code is to load around 100 png image files in a list of Texture Objects; these texture objects (TMyObject) has a "LoadFromFile" function which lasts around 60 ticks).

I have browsed lots of explanations concerning multithreading, and have therefore :

=> no success in all these tries.

Here below is my code that I tried to simplify. Any help or explanation would help to make my second Thread been executed.

//---------------------------------------------------------------------------
class TMainClass
{
private:
    TMyList<SmartPtr<TEvent> > mEventList;
    SmartPtr<TMyThread> mThread1, mThread2;
    int mCount;
protected:
    int mCurrent, mLast;
    TMyList<SmartPtr<TMyObject> > mObjectList;
    TMyObject *mpObject;    
    void MyInit();  
public:
    TMainObject(TMyParentObject *parent);
    virtual ~TMainObject();
    virtual void PeriodicTask();
};

//---------------------------------------------------------------------------
class TMyThread : public TThread
{
    TMyList<SmartPtr<TEvent> > *mpEventList;
    TMyList<SmartPtr<TMyObject> > *mpObjectList;
    int mStart, mEnd;
public:
    TMyThread(  TMyList<SmartPtr<TEvent> > *pEventList,
                TMyList<SmartPtr<TMyObject> > *pObjectList,
                int Start, int End);
    virtual void __fastcall Execute(void);
};

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
TMainClass::TMainClass(TMyParentObject *parent)
{    
    mCount = 0;
}

TMainClass::~TMainClass()
{
    if (mThread1.GetPtr() != NULL)
    {
        mThread1->Terminate();
        mThread1 = SmartPtr<TMyThread> (NULL);
    }
    if (mThread2.GetPtr() != NULL)
    {
        mThread2->Terminate();
        mThread2 = SmartPtr<TMyThread> (NULL);
    }
    mpObject = NULL;
    mObjectList.Clear();
    mEventList.Clear();
}

void TMainClass::MyInit()
{
    if (mThread1.GetPtr() != NULL) return;    
    mObjectList.Clear();
    mEventList.Clear();
    mCount = GetNumberOfFiles("C:/MyPath/");    
    for (int i = 1; i <= mCount; i++)
    {
        SmartPtr<TEvent> lEvent (new TEvent(NULL, false, false, ""));
        lEvent.GetPtr()->ResetEvent();
        mEventList.Add(lEvent);
    }
    mThread1 = SmartPtr<TMyThread> (new TMyThread(&mEventList, &mObjectList,    1,        floor(mCount/2.0) )); // lock before that ?
    mThread2 = SmartPtr<TMyThread> (new TMyThread(&mEventList, &mObjectList, floor(mCount/2.0)+1, mCount ));    // lock before that ?

    mCurrent = 0;
}

void TMainClass::PeriodicTask()
{
    mpObject = NULL;
    int lCount = mObjectList.Count();
    if (lCount != 0)
    {
        ++mCurrent;        
        mCurrent = min(mCurrent, lCount);
        if (    mLast != mCurrent
            &&  mEventList[mCurrent]->WaitFor(120) != wrSignaled    )
            return;
        mLast = mCurrent;
        mpObject = mObjectList[mCurrent].GetPtr(); // lock before that ?
    }
    if (mpObject == NULL) return;

    mpObject->MyObjectUtilisation();    // lock before that ?
}

//---------------------------------------------------------------------------
TMyThread::TMyThread(   TMyList<SmartPtr<TEvent> > *pEventList, TMyList<SmartPtr<TMyObject> > *pObjectList,
                        int Start, int End);
:TThread(false)
{
    mpEventList = pEventList;   // lock before that ?
    mpObjectList = pObjectList; // lock before that ?

    mStart = Start;
    mEnd = End;

    FreeOnTerminate = false;
}

void __fastcall TMyThread::Execute(void)
{
    for (int i = mStart; i <= mEnd; i++)
    {
        try
        {
            if (mpEventList != NULL && mpObjectList != NULL)
            {                
                SmartPtr<TMyObject> pObject (new TMyObject());
                pObject->LoadFromFile(i);
                // common memory accesses before which I want to put a lock
                mpObjectList->Insert(i,pObject);
                mpEventList[i]->SetEvent();
                // place where I could release this lock
            }
        }
        catch(Exception &e)
        {
            ShowMessage("Exception in Execute : " + e.Message);
        }
    }
    return;
}

Cheers, Arnaud.

Community
  • 1
  • 1
Arnaud
  • 109
  • 3
  • 15
  • Are you certain both threads have their `Execute` method called? Can you step through the code in a debugger to see what is happening? – Chris O Apr 06 '12 at 17:19
  • Also, can each thread instance run correctly, if you comment out the other instance creation? For thread #2 do `mStart` and `mEnd` have meaningful values? – Chris O Apr 06 '12 at 17:26
  • Well, I'm not going to redevelop your Texture objects, so I'll have to make do with something else that can load png files. TPngImage seems likea good bet? – Martin James Apr 06 '12 at 20:45
  • The thing is, with this task, you are trying too hard and have read too much. Now your head is exploding. The way to do things like this is to queue 'PNGloader' objects to a pool of threads and have the last load completion signal that the task is done and here is your vector of loaded PNG objects. I cannot stress too highly that micro-managing threads/Events in lists, then polling them, is bad. It just sucks. It will drive you insane and you will start doing odd things like calling Application.ProcessMessages. I will try to do a little example. – Martin James Apr 06 '12 at 21:59

1 Answers1

0

OK, I did an example of a PNG loader using threads, (actually a threadpool). It differs slightly from your design, but it works OK. I tried to get all the code in the header to make it easier to post, but the dependencies did not allow it and so I had to have a little cpp as well. I created a test form that displays a slide-show so I've included the code for that as well.

//HPP header:

#ifndef PNGloaderH
#define PNGloaderH

#include <Classes.hpp>
#include <deque.h>
#include <vector.h>
#include <PngImage.hpp>
#include <usefulStuff.hpp>

class CBthreadPool;

// Task class to inherit from, supplying the abstract run() method.
class CBtask {
    friend class CBthreadPool;
    friend class TpoolThread;
    CBthreadPool *myPool;
    TNotifyEvent FonComplete;
protected:
    int param;
    virtual void DoCompleted(){   // called after run, normall calls OnComplete
        if (FonComplete!=NULL){FonComplete((TObject*)this);};
        delete(this);
    }
public:
    String errorMess;
    CBtask(int inParam, TNotifyEvent OnComplete){  // user param and
        FonComplete=OnComplete;  // an OnComplete callback to be called after run()
        param=inParam;
    };
    virtual void run()=0;
    void submit(CBtask *aTask);
};

// Producer-Consumer queue for tasks
class CBSqueue{
    TCriticalSection *access;
    deque<CBtask*> workQueue;
    TSemaphore *queueSema;
public:
    CBSqueue(){
        access=new TCriticalSection();
        queueSema=new TSemaphore(NULL,0,MAXINT,NULL,false);
    };
    void push(CBtask *task){
        access->Acquire();
        workQueue.push_front(task);
        access->Release();
        queueSema->Release();
    };
    bool pop(CBtask **task,DWORD timeout){
        if(wrSignaled==queueSema->WaitFor(timeout)){
            access->Acquire();
            *task=workQueue.back();
            workQueue.pop_back();
            access->Release();
            return(true);
        } else return false;
    };
};

// Threadpool thread
class TpoolThread : public TThread{
    CBthreadPool *FmyPool;
protected:
     virtual void __fastcall Execute(void);
public:
    TpoolThread(CBthreadPool *myPool):TThread(true){
        FmyPool=myPool;
        Resume();
    };
};

// General purpose threadpool
class CBthreadPool {
    friend class TpoolThread;
    int threadCnt;
    CBSqueue taskQueue; // P-C queue of CBtask, (or descendants)
public:
    CBthreadPool(int numThreads){
        for(threadCnt=0;threadCnt<numThreads;threadCnt++){new TpoolThread(this);}
    } // crate all the thradpool threads, passing them the queue to wait on
    void submit(CBtask *aTask){ // method to submit work
        aTask->myPool=this; // in case the task wants to issue any tasks
        taskQueue.push(aTask);  // off it goes..
    };
};

/*  This CBtask descendant has  a 'run' method that loads 'thePNG' object
from a filespec passed in ctor. Instances of this class are queued to the
 thread pool to load the PNG files */
class PNGtask:public CBtask{
    String Ffolder;
public:
    TPngImage *PngImage;
    void run(){
        PngImage=new TPngImage;
        PngImage->LoadFromFile(Ffolder);
    };
    PNGtask(String dir,TNotifyEvent OnDone):CBtask(0,OnDone){Ffolder=dir;};
};

/* This CBtask descendant has  a 'run' method that iterates a folder passed in
the ctor for 'PNG' files, submits a PNGtask for each one and waits for the last
to complete.  One is issued to the pool for each folder to be searched */
class PNGfilesTask:public CBtask{
    String Ffolder;
    int taskCounter;
    TEvent *CompleteEvent;
    TCriticalSection *counterLock;
public:
    vector<TPngImage*> *PngImages;
    TObject *userData;
    PNGfilesTask(String folder, TNotifyEvent OnComplete,
                vector<TPngImage*> *PngImages,TObject *UserData):CBtask(0,OnComplete){
        Ffolder=folder;
        this->userData=UserData;
        CompleteEvent=new TEvent(NULL,false,false,"",false);
        counterLock=new TCriticalSection();
        this->PngImages=PngImages;
    }
    void run(){  // get all the file names
        TStringList *files=listAllFilesInThisFolderMatching(Ffolder,"*.png");
        taskCounter=files->Count; // set the task counter
        for (int i=0; i<files->Count; i++) {  // submit a PNGtask for each file
            PNGtask *aTask=new(PNGtask)(Ffolder+"\\"+files->Strings[i],OnFilesLoaded);
            submit(aTask);
        }
        delete(files);
        CompleteEvent->WaitFor(INFINITE); // and wait for the last one to finish
    };
    void __fastcall OnFilesLoaded(TObject *Sender){  // called by each PNGtask
        counterLock->Acquire();                        // thread-safe
        PngImages->push_back(((PNGtask*)Sender)->PngImage); // PNGImage into vector
        if(--taskCounter==0){   // all loads done?
            CompleteEvent->SetEvent();   // signal the run() method to continue
        };
        counterLock->Release();
    };
};

/* This class inherits from threadpool and has a 'PNGget' method that takes a
folder path, a pointer to a caller-supplied vector that will be loaded with
TPngImage* instances, an 'OnComplete' callback and a user context object.
When the files are all loaded, the 'OnComplete' event is called with the
PNGfilesTask object as the Sender. */
class PNGload:public CBthreadPool{
public:
    PNGload(int numThreads):CBthreadPool(numThreads){
    }
    void PNGget(String folder,vector<TPngImage*> *results,
                TNotifyEvent OnComplete, TObject *userData){
        PNGfilesTask *loadPNG=new(PNGfilesTask)(folder,OnComplete,results,userData);
        submit(loadPNG); // execute loadPNG->run() on the threadpool
    };
};

#endif

// CPP file

#include "PNGloader.hpp"

void __fastcall TpoolThread::Execute(){
    CBtask *thisTask;
    while(FmyPool->taskQueue.pop(&thisTask,INFINITE)){
        try{
            if(thisTask!=NULL){
                thisTask->errorMess="";
                thisTask->run();
            }
            else{
                FmyPool->taskQueue.push(thisTask);
                exit;
      }
        }
        catch(exception& e){
            thisTask->errorMess=e.what();
        }
        thisTask->DoCompleted();
        thisTask;
    }
};

void CBtask::submit(CBtask *aTask){
    myPool->submit(aTask);
}

// Test form - allows the user to select a folder and then shows a slide-show of any PNG files inside. It has a panel at the top for a button and a label, a timer and the lower section of the form is all a TImage.

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "PNGform.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TfoPNGload *foPNGload;
//---------------------------------------------------------------------------
__fastcall TfoPNGload::TfoPNGload(TComponent* Owner)
    : TForm(Owner)
{
    myLoad=new PNGload(10);
}
//---------------------------------------------------------------------------

void __fastcall TfoPNGload::SpeedButton1Click(TObject *Sender)
{
        if(PathName==""){PathName="C:\\";};
        if (SelectDirectory("Select Directory",PathName,PathName)){
            myLoad->PNGget(PathName,new(vector<TPngImage*>),filesLoaded,NULL);
        }
}
//---------------------------------------------------------------------------

void __fastcall TfoPNGload::filesLoaded(TObject *Sender){
    vector<TPngImage*> *PngImages;
    PngImages=((PNGfilesTask*)Sender)->PngImages;
    PostMessage(Handle,WM_APP,0,long(PngImages));
};

void __fastcall TfoPNGload::WMAPP(TMessage& msg){
    vector<TPngImage*> *thisVectorPtr=(vector<TPngImage*>*)msg.LParam;
    resultList.push_back(thisVectorPtr);
    vecSize=thisVectorPtr->size();
    if (vecSize>0) tiSlideShow->Enabled=true;
};


void __fastcall TfoPNGload::getNextVector(){
    if (resultList.size()!=0) {
        currentPNG=resultList.back();
        resultList.pop_back();
        currentPNGindex=0;
    }
    else
        currentPNG=NULL;
};

void __fastcall TfoPNGload::tiSlideShowTimer(TObject *Sender)
{
    if(currentPNG==NULL) getNextVector();
    else
        if (currentPNG->size()==0) {
            delete(currentPNG);
            getNextVector();
        }
    if(currentPNG==NULL) {
        tiSlideShow->Enabled=false;
        Label1->Caption="No files left";
        return;
    };
    TPngImage *thisPNG;
    Label1->Caption=IntToStr((int)currentPNG->size())+" PNG files left";
    thisPNG=currentPNG->back();
    currentPNG->pop_back();
    Image1->Picture->Assign(thisPNG);
    delete(thisPNG);
}
//---------------------------------------------------------------------------

// Form header

//---------------------------------------------------------------------------

#ifndef PNGformH
#define PNGformH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Buttons.hpp>
#include <Dialogs.hpp>
#include <ExtCtrls.hpp>
#include <PngImage.hpp>
#include "Pngloader.hpp"
#include "PNGform.h"

//---------------------------------------------------------------------------
class TfoPNGload : public TForm
{
__published:    // IDE-managed Components
    TImage *Image1;
    TPanel *Panel1;
    TSpeedButton *SpeedButton1;
    TTimer *tiSlideShow;
    TLabel *Label1;
    void __fastcall tiSlideShowTimer(TObject *Sender);
    void __fastcall SpeedButton1Click(TObject *Sender);
private:
    PNGload *myLoad;
    String PathName;
    void __fastcall filesLoaded(TObject *Sender);
    void __fastcall getNextVector();
protected:
    MESSAGE void __fastcall WMAPP(TMessage& msg);
public:     // User declarations
    __fastcall TfoPNGload(TComponent* Owner);

    BEGIN_MESSAGE_MAP
    MESSAGE_HANDLER(WM_APP, TMessage, WMAPP)
    END_MESSAGE_MAP(TForm)

    vector < vector<TPngImage*>* > resultList;
    vector<TPngImage*> *currentPNG;
    int currentPNGindex;
    int vecSize;
};
//---------------------------------------------------------------------------
extern PACKAGE TfoPNGload *foPNGload;
//---------------------------------------------------------------------------
#endif

Test form

Martin James
  • 24,453
  • 3
  • 36
  • 60
  • Thanks a lot for this answer. I temporarily achieved my goal with two separate pairs of lists. But I will try and understand your explanation as soon as possible. Thanks again in advance. – Arnaud Apr 11 '12 at 11:54