I am trying to utilize shared function (in main thread) and use it from 3 threads. The function will do some potentially lengthy operation like disk write and to avoid possible problems I will lock it. I use Indy IdThreadComponent and TCriticalSection. Here is how it looks:
//---------------------------------------------------------------------------
// In header file
//---------------------------------------------------------------------------
boost::scoped_ptr<TCriticalSection> csShared;
//---------------------------------------------------------------------------
// Main file
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
csShared.reset(new TCriticalSection);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::SharedFunction(UnicodeString TextData)
{
try
{
csShared->Enter(); // As suggested by Remy this is placed incorrectly and needs to be moved outside of try block
//Memo1->Lines->Add(TextData); // [EDIT] calling this within thread is wrong
Sleep(2000);
}
__finally
{
csShared->Leave();
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::IdThreadComponent1Run(TIdThreadComponent *Sender)
{
SharedFunction("Thread 1 calling");
IdThreadComponent1->Stop();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::IdThreadComponent2Run(TIdThreadComponent *Sender)
{
SharedFunction("Thread 2 calling");
IdThreadComponent2->Stop();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::IdThreadComponent3Run(TIdThreadComponent *Sender)
{
SharedFunction("Thread 3 calling");
IdThreadComponent3->Stop();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
IdThreadComponent1->Start();
IdThreadComponent2->Start();
IdThreadComponent3->Start();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose)
{
// Note - these 3 Stop() calls are used if threads are set to run infinitely
// But in this example it is not needed as they stop themselves
//IdThreadComponent1->Stop();
//IdThreadComponent2->Stop();
//IdThreadComponent3->Stop();
// Now wait for lock to be released [WRONG - COMMENTED IN EDIT]
//while (!csShared->TryEnter())
// {
// Sleep(500);
// }
//csShared->Leave();
// [EDIT v1] easier and faster way to wait than above
//csShared->Enter();
//csShared->Leave();
// [EDIT v2] block exit until all threads are done
while (IdThreadComponent1->Active || IdThreadComponent2->Active || IdThreadComponent3->Active)
{
Sleep(200); // make wait loop less CPU intensive
};
CanClose = true;
}
//---------------------------------------------------------------------------
The problems:
– If I close window quickly (only one thread executes the function, it never leaves program – waits forever, and in debugger only first thread exits, other two don’t). I am using OnCloseQuery event to check if threads are done. What I am doing wrong?
[EDIT] After removing Memo1->Lines->Add(TextData); as suggested in comments by David Heffernan, it exits properly so this part of question is solved and the following remains:
-
Is it fine to call
csShared->Enter();inside the shared function like above or do I have to call it outside of it in each thread like this:void __fastcall TForm1::IdThreadComponent1Run(TIdThreadComponent *Sender) { csShared->Enter(); SharedFunction("Thread 1 calling"); csShared->Leave(); IdThreadComponent1->Stop(); } -
Is this better than the above version (calling
csShared->Enter();within the function itself)? Or the same? Both versions seem to work fine, I am wondering is there a difference, because the first one is cleaner.
I do not need Synchronize in case if you are wondering, this will be for disk write and not for updating VCL, so the above SharedFunction is just for the example purpose.
It is fine, even desirable, to put the calls to
Enter()andLeave()inside of the shared function. However, if you are going to usetry/__finallythen you need to put theEnter()outside of thetry, in case it fails. You don’t want toLeave()something you did not successfulEnter()into, eg:Since you are using Boost anyway, you should make use of its own
mutexandlockclasses instead, then you don’t have to worry abouttry/__finally,Enter(), orLeave()at all, eg:As for the
TMemoaccess, use theTIdSyncorTIdNotifyclass to execute that code in a thread-safe manner, eg: