【问题标题】:Waiting for IdThreadComponent to finish on program exit等待 IdThreadComponent 在程序退出时完成
【发布时间】:2012-12-03 22:00:31
【问题描述】:

我正在尝试利用共享函数(在主线程中)并从 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 在 cmets 中的建议删除 Memo1-&gt;Lines-&gt;Add(TextData); 后,它会正确退出,因此这部分问题得到解决,以下内容仍然存在:

  • 可以像上面那样在共享函数内部调用csShared-&gt;Enter();,还是必须像这样在每个线程的外部调用它:

    void __fastcall TForm1::IdThreadComponent1Run(TIdThreadComponent *Sender)
    {
    csShared->Enter();
    SharedFunction("Thread 1 calling");
    csShared->Leave();
    IdThreadComponent1->Stop();
    }
    
  • 这是否比上面的版本更好(在函数本身内调用csShared-&gt;Enter();)?还是一样?两个版本似乎都可以正常工作,我想知道有什么区别,因为第一个更干净。

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

【问题讨论】:

  • 你知道你不能在 GUI 线程之外做 GUI 工作吗?您的程序似乎违反了该规则。
  • 假设我没有在那个函数中放入备忘录中的东西。共享函数只是一个示例,但实际上它会写入磁盘并且可能很长。
  • TryEnter 周围的循环是一场灾难。只需调用 Enter。这将一直阻塞,直到获得锁为止。
  • 不,它不起作用。在线程完成之前它不会阻塞。它只是获取然后释放一个锁。如果你想阻塞直到发生某些事情,你需要一个等待函数。
  • 这仍然不是一个很好的等待函数,因为它会以 100% CPU 旋转。

标签: multithreading delphi c++builder indy


【解决方案1】:

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/__finallyEnter()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);
}

对于TMemo 访问,使用TIdSyncTIdNotify 类以线程安全的方式执行该代码,例如:

#include <IdSync.hpp>

class TMemoNotify : public TIdNotify
{
protected:
    String TextData;

    void __fastcall DoNotify()
    {
        Form1->Memo1->Lines->Add(TextData);
    }

public:
    __fastcall TMemoNotify(const String &ATextData) : TextData(ATextData) {}
};


void __fastcall TForm1::SharedFunction(UnicodeString TextData)
{
    ...
    (new TMemoNotify(TextData))->Notify(); // TIdNotify is self-freeing
    ...
}

【讨论】:

  • 您的优秀答案系列中的又一个雷米!始终抓住重点。
  • 我可能会纠正你,递归互斥锁的正确标头是 #include &lt;boost/thread/recursive_mutex.hpp&gt;
【解决方案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->Enter() 可以吗?在上面的共享函数内部还是我必须在每个线程中像这样在外部调用它:

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

  • 如果要序列化整个函数体,我认为最好将关键部分 Enter 放在函数内(作为第一行),因为恕我直言,这会导致调用中的代码更简洁网站,同时确保在调用函数之前不会忘记进入关键部分。
  • 如果函数调用是更复杂操作的一部分,则必须在该操作开始时调用 CriticalSection 输入,显然在函数体之外。
  • 函数的某些部分也可以并行运行,因此您必须支持并发并调用 CriticalSection。仅在函数内部需要时才输入。

请记住,每个关键部分都是一个瓶颈。一个有意的,但您必须谨慎使用它,以避免在不需要的地方引入等待。

【讨论】:

  • 当然是瓶颈,但磁盘操作和功能也需要一致的磁盘数据(我将实际应用它),因此需要阻塞。虽然答案很好,我会将此标记为答案,但我只能标记一个,而雷米的只是一点点。所以我对此投了赞成票(其他人也应该投票,这两个都是很好的答案),无论如何我感谢你的解释!我在这里学到了很多东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-15
相关资源
最近更新 更多