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)