wxThread::Delete 永远挂起
wxThread::Delete hangs forever
我正在尝试使用 wxThreadHelper mixin 创建多线程 wxWidgets 应用程序。我正在研究 Windows,但整个想法是独立于平台的。我基本上是按照在线示例进行操作,但肯定有一些我不明白的地方,因为一旦我调用 Delete()
函数,应用程序就会挂起,永远等待。问题是辅助线程似乎也挂起。你知道我错过了什么吗?
这是我能得到的最小值:
#include <wx/wx.h>
class TestFrame: public wxFrame, public wxThreadHelper {
wxStaticText *m_text_time;
public:
TestFrame() : wxFrame(nullptr, wxID_ANY, wxT("Test")),
m_text_time(new wxStaticText(this, wxID_ANY, wxNow()))
{
if (CreateThread(wxTHREAD_DETACHED) != wxTHREAD_NO_ERROR) {
wxLogError("Could not create the worker thread!");
return;
}
if (GetThread()->Run() != wxTHREAD_NO_ERROR) {
wxLogError("Could not run the worker thread!");
return;
}
}
private:
wxThread::ExitCode Entry() override {
while (!GetThread()->TestDestroy()) {
wxSleep(2);
m_text_time->SetLabel(wxNow());
}
return (wxThread::ExitCode)nullptr;
}
void OnClose(wxCloseEvent& event) {
auto t = GetThread();
if (t) {
if (t->Delete() != wxTHREAD_NO_ERROR) {
wxLogMessage("Error: Can't delete the worker thread!");
}
}
event.Skip();
}
wxDECLARE_EVENT_TABLE();
};
wxDEFINE_EVENT(myEVT_THREAD_UPDATE, wxThreadEvent);
wxBEGIN_EVENT_TABLE(TestFrame, wxFrame)
EVT_CLOSE(TestFrame::OnClose)
wxEND_EVENT_TABLE()
class TestApp : public wxApp
{
wxFrame *m_frame = nullptr;
public:
bool OnInit() override {
m_frame = new TestFrame;
m_frame->Show();
return true;
}
};
IMPLEMENT_APP(TestApp)
这里有很多问题。要回答为什么这会挂起您的程序的主要问题,请在 OnClose
方法中调用 Destroy()
,然后删除框架(包括 m_text_time
)。同时,您的线程正在休眠,当它醒来时,它会尝试更改不再存在的 m_text_time
的标签。我不确定为什么这会导致死锁而不是段错误,但这就是问题所在。因此,您可以通过简单地将顺序更改为:
来使这项工作变得糟糕且有问题
m_text_time->SetLabel(wxNow());
wxSleep(2);
但请不要以此作为解决方案。这里还有很多问题。
首先,永远不要尝试从辅助线程访问 GUI 功能。不要从您的线程调用 m_text_time->SetLabel(wxNow());
,您应该将线程事件抛回主线程并让它更改标签。我将在下面展示如何执行此操作。
其次,对于分离的线程,您不应该在调用 运行 后尝试访问它。如果一个线程被分离,你应该假设它可以随时停止并删除自己。所以在你的 OnClose
方法中,你不应该调用 GetThread
。如果您想使用分离线程,则需要完全重新制定 OnClose
。但是对于这个例子,我认为一个可连接的线程是完全足够的,最简单的改变是将 if (CreateThread(wxTHREAD_DETACHED)...
更改为 if (CreateThread(wxTHREAD_JOINABLE)...
第三,休眠然后使用TestDestroy 确定何时退出会导致性能低下。在这种情况下,当你调用 Destroy 时,你的线程仍然要等到它被唤醒后才能退出。更好的方法是让您的线程等待 2 秒,直到收到信号。如果有信号,则立即退出;如果没有信号,它将在 2 秒后重复循环。有很多方法可以做到这一点,但我在下面展示了一种使用称为信号量的对象来做到这一点的方法。
将所有这些放在一起,这是您的代码的修改版本:
#include <wx/wx.h>
wxDEFINE_EVENT(myEVT_THREAD_UPDATE, wxThreadEvent);
class TestFrame: public wxFrame, public wxThreadHelper {
wxStaticText *m_text_time;
public:
TestFrame() : wxFrame(nullptr, wxID_ANY, wxT("Test")),
m_text_time(new wxStaticText(this, wxID_ANY, wxNow()))
{
if (CreateThread(wxTHREAD_JOINABLE) != wxTHREAD_NO_ERROR) {
wxLogError("Could not create the worker thread!");
return;
}
if (GetThread()->Run() != wxTHREAD_NO_ERROR) {
wxLogError("Could not run the worker thread!");
return;
}
Bind(myEVT_THREAD_UPDATE, &TestFrame::OnThreadUpdate, this);
}
private:
wxThread::ExitCode Entry() override {
while ( !GetThread()->TestDestroy() ) {
wxThreadEvent* event = new wxThreadEvent(myEVT_THREAD_UPDATE);
event->SetString(wxNow());
wxQueueEvent(this,event);
if ( m_shutdownSemaphore.WaitTimeout(2000) != wxSEMA_TIMEOUT )
{
break;
}
}
return (wxThread::ExitCode)0;
}
void OnClose(wxCloseEvent& event) {
auto t = GetThread();
if (t) {
m_shutdownSemaphore.Post();
t->Wait();
}
event.Skip();
}
void OnThreadUpdate(wxThreadEvent& event)
{
m_text_time->SetLabel(event.GetString());
}
wxSemaphore m_shutdownSemaphore;
wxDECLARE_EVENT_TABLE();
};
wxBEGIN_EVENT_TABLE(TestFrame, wxFrame)
EVT_CLOSE(TestFrame::OnClose)
wxEND_EVENT_TABLE()
class TestApp : public wxApp
{
wxFrame *m_frame = nullptr;
public:
bool OnInit() override {
m_frame = new TestFrame;
m_frame->Show();
return true;
}
};
IMPLEMENT_APP(TestApp)
我正在尝试使用 wxThreadHelper mixin 创建多线程 wxWidgets 应用程序。我正在研究 Windows,但整个想法是独立于平台的。我基本上是按照在线示例进行操作,但肯定有一些我不明白的地方,因为一旦我调用 Delete()
函数,应用程序就会挂起,永远等待。问题是辅助线程似乎也挂起。你知道我错过了什么吗?
这是我能得到的最小值:
#include <wx/wx.h>
class TestFrame: public wxFrame, public wxThreadHelper {
wxStaticText *m_text_time;
public:
TestFrame() : wxFrame(nullptr, wxID_ANY, wxT("Test")),
m_text_time(new wxStaticText(this, wxID_ANY, wxNow()))
{
if (CreateThread(wxTHREAD_DETACHED) != wxTHREAD_NO_ERROR) {
wxLogError("Could not create the worker thread!");
return;
}
if (GetThread()->Run() != wxTHREAD_NO_ERROR) {
wxLogError("Could not run the worker thread!");
return;
}
}
private:
wxThread::ExitCode Entry() override {
while (!GetThread()->TestDestroy()) {
wxSleep(2);
m_text_time->SetLabel(wxNow());
}
return (wxThread::ExitCode)nullptr;
}
void OnClose(wxCloseEvent& event) {
auto t = GetThread();
if (t) {
if (t->Delete() != wxTHREAD_NO_ERROR) {
wxLogMessage("Error: Can't delete the worker thread!");
}
}
event.Skip();
}
wxDECLARE_EVENT_TABLE();
};
wxDEFINE_EVENT(myEVT_THREAD_UPDATE, wxThreadEvent);
wxBEGIN_EVENT_TABLE(TestFrame, wxFrame)
EVT_CLOSE(TestFrame::OnClose)
wxEND_EVENT_TABLE()
class TestApp : public wxApp
{
wxFrame *m_frame = nullptr;
public:
bool OnInit() override {
m_frame = new TestFrame;
m_frame->Show();
return true;
}
};
IMPLEMENT_APP(TestApp)
这里有很多问题。要回答为什么这会挂起您的程序的主要问题,请在 OnClose
方法中调用 Destroy()
,然后删除框架(包括 m_text_time
)。同时,您的线程正在休眠,当它醒来时,它会尝试更改不再存在的 m_text_time
的标签。我不确定为什么这会导致死锁而不是段错误,但这就是问题所在。因此,您可以通过简单地将顺序更改为:
m_text_time->SetLabel(wxNow());
wxSleep(2);
但请不要以此作为解决方案。这里还有很多问题。
首先,永远不要尝试从辅助线程访问 GUI 功能。不要从您的线程调用 m_text_time->SetLabel(wxNow());
,您应该将线程事件抛回主线程并让它更改标签。我将在下面展示如何执行此操作。
其次,对于分离的线程,您不应该在调用 运行 后尝试访问它。如果一个线程被分离,你应该假设它可以随时停止并删除自己。所以在你的 OnClose
方法中,你不应该调用 GetThread
。如果您想使用分离线程,则需要完全重新制定 OnClose
。但是对于这个例子,我认为一个可连接的线程是完全足够的,最简单的改变是将 if (CreateThread(wxTHREAD_DETACHED)...
更改为 if (CreateThread(wxTHREAD_JOINABLE)...
第三,休眠然后使用TestDestroy 确定何时退出会导致性能低下。在这种情况下,当你调用 Destroy 时,你的线程仍然要等到它被唤醒后才能退出。更好的方法是让您的线程等待 2 秒,直到收到信号。如果有信号,则立即退出;如果没有信号,它将在 2 秒后重复循环。有很多方法可以做到这一点,但我在下面展示了一种使用称为信号量的对象来做到这一点的方法。
将所有这些放在一起,这是您的代码的修改版本:
#include <wx/wx.h>
wxDEFINE_EVENT(myEVT_THREAD_UPDATE, wxThreadEvent);
class TestFrame: public wxFrame, public wxThreadHelper {
wxStaticText *m_text_time;
public:
TestFrame() : wxFrame(nullptr, wxID_ANY, wxT("Test")),
m_text_time(new wxStaticText(this, wxID_ANY, wxNow()))
{
if (CreateThread(wxTHREAD_JOINABLE) != wxTHREAD_NO_ERROR) {
wxLogError("Could not create the worker thread!");
return;
}
if (GetThread()->Run() != wxTHREAD_NO_ERROR) {
wxLogError("Could not run the worker thread!");
return;
}
Bind(myEVT_THREAD_UPDATE, &TestFrame::OnThreadUpdate, this);
}
private:
wxThread::ExitCode Entry() override {
while ( !GetThread()->TestDestroy() ) {
wxThreadEvent* event = new wxThreadEvent(myEVT_THREAD_UPDATE);
event->SetString(wxNow());
wxQueueEvent(this,event);
if ( m_shutdownSemaphore.WaitTimeout(2000) != wxSEMA_TIMEOUT )
{
break;
}
}
return (wxThread::ExitCode)0;
}
void OnClose(wxCloseEvent& event) {
auto t = GetThread();
if (t) {
m_shutdownSemaphore.Post();
t->Wait();
}
event.Skip();
}
void OnThreadUpdate(wxThreadEvent& event)
{
m_text_time->SetLabel(event.GetString());
}
wxSemaphore m_shutdownSemaphore;
wxDECLARE_EVENT_TABLE();
};
wxBEGIN_EVENT_TABLE(TestFrame, wxFrame)
EVT_CLOSE(TestFrame::OnClose)
wxEND_EVENT_TABLE()
class TestApp : public wxApp
{
wxFrame *m_frame = nullptr;
public:
bool OnInit() override {
m_frame = new TestFrame;
m_frame->Show();
return true;
}
};
IMPLEMENT_APP(TestApp)