DLL 中的线程在 Synchronize() 上挂起,直到线程终止
Thread in DLL hangs on Synchronize() until thread is terminated
我在 C++Builder XE6 中编写了一个 DLL,它使用一个线程来执行一些后台任务。
这是 DLL 中的(简化)代码:
typedef void (__stdcall* ERRORCALLBACK)();
class TMyThread : public TThread
{
typedef TThread inherited;
private:
void __fastcall DoError ();
public:
inline __fastcall TMyThread () : inherited(true) {}
ERRORCALLBACK OnError;
};
void __fastcall TMyThread::Execute ()
{
AddLog("Thread started (ID: " + UIntToStr(ThreadID) + ")");
try
{
while (!Terminated)
{
if (true) // Some error condition
{
AddLog("Before Synchronize(DoError)");
if (OnError)
Synchronize(DoError);
AddLog("After Synchronize(DoError)");
}
}
}
__finally
{
AddLog("Thread stopped (ID: " + UIntToStr(ThreadID) + ")");
}
}
void __fastcall TMyThread::DoError ()
{
AddLog("DoError() begins");
try
{
OnError();
}
catch (...)
{
}
AddLog("DoError() ends");
}
TMyThread* Thread = NULL;
void __declspec(dllexport) __stdcall StartThread (ERRORCALLBACK ErrorProc)
{
if (!Thread)
{
Thread = new TMyThread();
Thread->OnError = ErrorProc;
Thread->Start();
}
}
void __declspec(dllexport) __stdcall StopThread ()
{
if (Thread)
{
AddLog("Stopping thread (ID: " + UIntToStr(Thread->ThreadID) + ")");
Thread->Terminate();
Thread->WaitFor();
delete Thread;
Thread = NULL;
}
}
AddLog()
函数将文本字符串写入日志文件。
DLL 由 VCL 应用程序使用,用 C++Builder 1 编写。
这是 VCL 应用程序中的代码:
typedef void (__stdcall* ERRORCALLBACK)();
extern "C"
{
void __declspec(dllimport) __stdcall StartThread (ERRORCALLBACK ErrorProc);
void __declspec(dllimport) __stdcall StopThread ();
}
void __stdcall ThreadError ()
{
Form1->Memo1->Lines->Add("An error occurred!");
}
void __fastcall TForm1::Button1Click (TObject *Sender)
{
StartThread(ThreadError);
}
void __fastcall TForm1::Button2Click (TObject *Sender)
{
StopThread();
}
我 运行 VCL 应用程序,单击 Button1 启动线程,等待几秒钟,然后单击 Button2 停止线程。
之后,日志文件包含:
18-05-2022 13:52:22.798: Thread started (ID: 2756)
18-05-2022 13:52:22.804: Before Synchronize(DoError)
18-05-2022 13:52:45.530: Stopping thread (ID: 2756)
18-05-2022 13:52:45.530: DoError() begins
18-05-2022 13:52:45.530: DoError() ends
18-05-2022 13:52:45.530: After Synchronize(DoError)
18-05-2022 13:52:45.530: Thread stopped (ID: 2756)
显然,对 Synchronize()
的调用挂起,直到线程终止。
为什么 Synchronize()
挂起?我该如何解决这个问题?
更新
我找到了一个 similar question,这解释了为什么 Synchronize()
挂起;因为 CheckSynchronize()
没有被调用。
C++Builder 1 没有 CheckSynchronize()
函数,TApplication::Idle()
似乎也不会检查与线程同步相关的任何内容。
我仍然不知道如何解决这个问题。
您是正确的,TThread::Synchronize()
挂起的原因是因为它的请求没有被处理。线程终止时请求得到处理的原因是因为 TThread::WaitFor()
在等待线程完全终止时处理 TThread::Synchronize()
请求。
为了解决这个问题,您的 DLL 必须从 DLL 内部导出第三个调用其自己的 CheckSynchronize()
函数(XE6 中确实存在)的函数,然后 EXE 将需要调用该函数定期,例如在计时器中。
DLL:
void __declspec(dllexport) __stdcall CheckThreadSyncs ()
{
CheckSynchronize();
}
应用程序:
extern "C"
{
...
void __declspec(dllimport) __stdcall CheckThreadSyncs ();
}
__fastcall TForm1::TForm1 (TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::Timer1Timer (TObject* Sender)
{
CheckThreadSyncs();
}
另一种解决方案是让 DLL 线程调用 OnError
回调 而不用 使用 Synchronize()
,然后让 EXE 的处理程序执行自己的同步如所须。例如,在 C++Builder 1 中,您可以使用 Win32 SendMessage()
函数,因为 TThread::Synchronize()
在该版本中既不是 public
也不是 static
。
DLL:
void __fastcall TMyThread::Execute ()
{
...
if (OnError)
DoError();
...
}
应用程序:
// sending to a private hidden HWND because TForm::Handle is not thread-safe!
HWND hThreadErrorWnd = NULL;
const UINT WM_THREAD_ERROR = WM_APP + 1;
void __stdcall ThreadError ()
{
SendMessage(hThreadErrorWnd, WM_THREAD_ERROR, 0, 0);
}
__fastcall TForm1::TForm1 (TComponent* Owner)
: TForm(Owner)
{
hThreadErrorWnd = AllocateHWnd(&ThreadErrorWndProc);
}
__fastcall TForm1::~TForm1 ()
{
DeallocateHWnd(hThreadErrorWnd);
}
void __fastcall TForm1::ThreadErrorWndProc (TMessage &Message)
{
if (Message.Msg == WM_THREAD_ERROR)
Memo1->Lines->Add("An error occurred!");
else
Message.Result = DefWindowProc(hThreadErrorWnd, Message.Msg, Message.WParam, Message.LParam);
}
C++Builder 1 doesn't have a CheckSynchronize() function
正确。 CheckSynchronize()
是在 Delphi/C++Builder 6 中引入的,当时 TThread::Synchronize()
是 re-written 以支持 cross-platform(当 Delphi/C++Builder 6 首次引入时short-lived CLX 框架,用于 Windows 和 Linux 以及 cross-platform 版本的 VCL)。
这并不重要,即使 CheckSynchronize()
确实存在于 C++Builder 1 中,因为根本问题是 DLL 和 EXE 不共享 RTL 库的单个实例,所以任何EXE 对其 RTL 的处理不会影响 DLL 的 RTL 中的任何内容,包括处理 Synchronize()
请求。这就是为什么您必须公开访问 DLL 版本的 CheckSynchronize()
.
nor does TApplication::Idle() seem to check anything related to thread synchronization.
事实上,确实如此,只是不是你想的那样。在 Delphi/C++Builder 6 之前,TThread::Synchronize()
简单地调用 SendMessage()
发送请求到主 RTL-owned window 运行 UI线程。主线程的标准消息循环将在其正常的消息调度期间处理这些请求。在 Delphi/C++Builder 6 之后不再是这种情况,请求现在被放入队列,主线程定期调用 CheckSynchronize()
来处理队列。
我在 C++Builder XE6 中编写了一个 DLL,它使用一个线程来执行一些后台任务。
这是 DLL 中的(简化)代码:
typedef void (__stdcall* ERRORCALLBACK)();
class TMyThread : public TThread
{
typedef TThread inherited;
private:
void __fastcall DoError ();
public:
inline __fastcall TMyThread () : inherited(true) {}
ERRORCALLBACK OnError;
};
void __fastcall TMyThread::Execute ()
{
AddLog("Thread started (ID: " + UIntToStr(ThreadID) + ")");
try
{
while (!Terminated)
{
if (true) // Some error condition
{
AddLog("Before Synchronize(DoError)");
if (OnError)
Synchronize(DoError);
AddLog("After Synchronize(DoError)");
}
}
}
__finally
{
AddLog("Thread stopped (ID: " + UIntToStr(ThreadID) + ")");
}
}
void __fastcall TMyThread::DoError ()
{
AddLog("DoError() begins");
try
{
OnError();
}
catch (...)
{
}
AddLog("DoError() ends");
}
TMyThread* Thread = NULL;
void __declspec(dllexport) __stdcall StartThread (ERRORCALLBACK ErrorProc)
{
if (!Thread)
{
Thread = new TMyThread();
Thread->OnError = ErrorProc;
Thread->Start();
}
}
void __declspec(dllexport) __stdcall StopThread ()
{
if (Thread)
{
AddLog("Stopping thread (ID: " + UIntToStr(Thread->ThreadID) + ")");
Thread->Terminate();
Thread->WaitFor();
delete Thread;
Thread = NULL;
}
}
AddLog()
函数将文本字符串写入日志文件。
DLL 由 VCL 应用程序使用,用 C++Builder 1 编写。
这是 VCL 应用程序中的代码:
typedef void (__stdcall* ERRORCALLBACK)();
extern "C"
{
void __declspec(dllimport) __stdcall StartThread (ERRORCALLBACK ErrorProc);
void __declspec(dllimport) __stdcall StopThread ();
}
void __stdcall ThreadError ()
{
Form1->Memo1->Lines->Add("An error occurred!");
}
void __fastcall TForm1::Button1Click (TObject *Sender)
{
StartThread(ThreadError);
}
void __fastcall TForm1::Button2Click (TObject *Sender)
{
StopThread();
}
我 运行 VCL 应用程序,单击 Button1 启动线程,等待几秒钟,然后单击 Button2 停止线程。 之后,日志文件包含:
18-05-2022 13:52:22.798: Thread started (ID: 2756)
18-05-2022 13:52:22.804: Before Synchronize(DoError)
18-05-2022 13:52:45.530: Stopping thread (ID: 2756)
18-05-2022 13:52:45.530: DoError() begins
18-05-2022 13:52:45.530: DoError() ends
18-05-2022 13:52:45.530: After Synchronize(DoError)
18-05-2022 13:52:45.530: Thread stopped (ID: 2756)
显然,对 Synchronize()
的调用挂起,直到线程终止。
为什么 Synchronize()
挂起?我该如何解决这个问题?
更新
我找到了一个 similar question,这解释了为什么 Synchronize()
挂起;因为 CheckSynchronize()
没有被调用。
C++Builder 1 没有 CheckSynchronize()
函数,TApplication::Idle()
似乎也不会检查与线程同步相关的任何内容。
我仍然不知道如何解决这个问题。
您是正确的,TThread::Synchronize()
挂起的原因是因为它的请求没有被处理。线程终止时请求得到处理的原因是因为 TThread::WaitFor()
在等待线程完全终止时处理 TThread::Synchronize()
请求。
为了解决这个问题,您的 DLL 必须从 DLL 内部导出第三个调用其自己的 CheckSynchronize()
函数(XE6 中确实存在)的函数,然后 EXE 将需要调用该函数定期,例如在计时器中。
DLL:
void __declspec(dllexport) __stdcall CheckThreadSyncs ()
{
CheckSynchronize();
}
应用程序:
extern "C"
{
...
void __declspec(dllimport) __stdcall CheckThreadSyncs ();
}
__fastcall TForm1::TForm1 (TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::Timer1Timer (TObject* Sender)
{
CheckThreadSyncs();
}
另一种解决方案是让 DLL 线程调用 OnError
回调 而不用 使用 Synchronize()
,然后让 EXE 的处理程序执行自己的同步如所须。例如,在 C++Builder 1 中,您可以使用 Win32 SendMessage()
函数,因为 TThread::Synchronize()
在该版本中既不是 public
也不是 static
。
DLL:
void __fastcall TMyThread::Execute ()
{
...
if (OnError)
DoError();
...
}
应用程序:
// sending to a private hidden HWND because TForm::Handle is not thread-safe!
HWND hThreadErrorWnd = NULL;
const UINT WM_THREAD_ERROR = WM_APP + 1;
void __stdcall ThreadError ()
{
SendMessage(hThreadErrorWnd, WM_THREAD_ERROR, 0, 0);
}
__fastcall TForm1::TForm1 (TComponent* Owner)
: TForm(Owner)
{
hThreadErrorWnd = AllocateHWnd(&ThreadErrorWndProc);
}
__fastcall TForm1::~TForm1 ()
{
DeallocateHWnd(hThreadErrorWnd);
}
void __fastcall TForm1::ThreadErrorWndProc (TMessage &Message)
{
if (Message.Msg == WM_THREAD_ERROR)
Memo1->Lines->Add("An error occurred!");
else
Message.Result = DefWindowProc(hThreadErrorWnd, Message.Msg, Message.WParam, Message.LParam);
}
C++Builder 1 doesn't have a CheckSynchronize() function
正确。 CheckSynchronize()
是在 Delphi/C++Builder 6 中引入的,当时 TThread::Synchronize()
是 re-written 以支持 cross-platform(当 Delphi/C++Builder 6 首次引入时short-lived CLX 框架,用于 Windows 和 Linux 以及 cross-platform 版本的 VCL)。
这并不重要,即使 CheckSynchronize()
确实存在于 C++Builder 1 中,因为根本问题是 DLL 和 EXE 不共享 RTL 库的单个实例,所以任何EXE 对其 RTL 的处理不会影响 DLL 的 RTL 中的任何内容,包括处理 Synchronize()
请求。这就是为什么您必须公开访问 DLL 版本的 CheckSynchronize()
.
nor does TApplication::Idle() seem to check anything related to thread synchronization.
事实上,确实如此,只是不是你想的那样。在 Delphi/C++Builder 6 之前,TThread::Synchronize()
简单地调用 SendMessage()
发送请求到主 RTL-owned window 运行 UI线程。主线程的标准消息循环将在其正常的消息调度期间处理这些请求。在 Delphi/C++Builder 6 之后不再是这种情况,请求现在被放入队列,主线程定期调用 CheckSynchronize()
来处理队列。