等待IdThreadComponent完成程序退出

时间:2012-12-03 22:00:31

标签: multithreading delphi c++builder indy

我正在尝试使用共享功能(在主线程中)并使用3个线程。该函数将执行一些可能冗长的操作,如磁盘写入,并避免可能的问题,我将锁定它。我使用Indy IdThreadComponentTCriticalSection。以下是它的外观:

//---------------------------------------------------------------------------
// 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;
}
//---------------------------------------------------------------------------

问题:

- 如果我快速关闭窗口(只有一个线程执行该函数,它永远不会离开程序 - 永远等待,而在调试器中只有第一个线程退出,其他两个不执行)。我正在使用OnCloseQuery事件来检查线程是否完成。我做错了什么?

[编辑]按照David Heffernan的评论中的建议删除Memo1->Lines->Add(TextData);后,它会正常退出,以便解决这部分问题并保留以下内容:

  • 在上面的共享函数中调用csShared->Enter();是否可以,或者我必须在每个线程中调用它,如下所示:

    void __fastcall TForm1::IdThreadComponent1Run(TIdThreadComponent *Sender)
    {
    csShared->Enter();
    SharedFunction("Thread 1 calling");
    csShared->Leave();
    IdThreadComponent1->Stop();
    }
    
  • 这比上面的版本更好(在函数本身中调用csShared->Enter();)吗?或者相同?两个版本似乎都运行良好,我想知道是否有区别,因为第一个版本更清洁。

如果您想知道,我不需要Synchronize,这将用于磁盘写入而不是用于更新VCL,因此上述SharedFunction仅用于示例目的。

2 个答案:

答案 0 :(得分:3)

Enter()Leave()的调用放在共享函数中是很好的,甚至是可取的。但是,如果您要使用try/__finally,则需要将Enter()放在try之外,以防它失败。您不希望Leave()您未成功Enter()的内容,例如:

void __fastcall TForm1::SharedFunction(UnicodeString TextData)
{
    csShared->Enter();
    try
    {
        //...
        Sleep(2000);
    }
    __finally
    {
        csShared->Leave();
    }
}

void __fastcall TForm1::IdThreadComponent1Run(TIdThreadComponent *Sender)
{
    SharedFunction("Thread 1 calling");
    IdThreadComponent1->Stop();
}

由于您仍在使用Boost,因此您应该使用自己的mutexlock类,然后您不必担心try/__finally,{{1} },或Enter(),例如:

Leave()

对于#include <boost/thread/recursive_mutex.hpp> #include <boost/thread/locks.hpp> boost::recursive_mutex mutex; void __fastcall TForm1::SharedFunction(UnicodeString TextData) { boost::lock_guard<boost::recursive_mutex> lock(mutex); //... Sleep(2000); } 访问,使用TMemoTIdSync类以线程安全的方式执行该代码,例如:

TIdNotify

答案 1 :(得分:2)

  

我正在尝试利用共享功能(在主线程中)并从3个线程中使用它。

方法或过程(通常是一段代码)不属于线程本身。任何代码都可以从应用程序中的任何线程调用,并且如果以并发方式从不同的线程调用,它可以同时运行多次。

例如

procedure A();
begin
  //do some work.
end;

你可以这样执行:

main thread
     |
  SomeFunc();
     |
     |      spawns
     |     thread X  
     |---------|
     |         |
     |         |
     |      OtherF()
    A()        |
     |         |   spawns thread Y
     |         |-------------|
     |        A()            |
     |         |             |
     |         |            A()
     |         |             | 
     |         |             |
  t1>|         |             |
     |         |             |
   A returns   |             |
    B()        |             |
     |         |             |
  t2>|         |             |
     |         |             |
     |        A returns      |
     |        thread end     |
     |                       |
     |                       |
  t3>|                       |
     |                      A returns
     |                      thread end
     |
   program end

在t1,3个不同的线程正在运行函数A(),在t2,2个线程仍在运行它(X和Y),在t3只有一个线程执行该函数(线程Y)。

  

调用csShared-&gt; Enter();在像上面这样的共享函数内部,或者我必须在每个线程中调用它,如下所示:

这取决于你。您必须定义调用它的位置,因为您有责任定义哪些代码片段必须仅在一个线程的上下文中运行,而其他代码将等待此操作完成以启动(串行执行)。

  • 如果整个函数体要序列化,我认为最好把关键部分Enter放在函数内部(作为第一行),因为恕我直言这导致调用中的代码更清晰站点,同时确保在调用函数之前不能忘记进入临界区。
  • 如果函数调用是更复杂操作的一部分,则必须在函数体外部调用CriticalSection在该操作的开头输入。
  • 也可以让部分功能可以并行运行,因此您必须支持并发,并且仅在需要时才调用CriticalSection.Enter。

记住每个CriticalSection都是瓶颈。故意的,但你必须谨慎使用它,以避免在不需要的地方引入等待。