Using Threads (pthreads) Within a Class Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
User avatar
Deluge
Earned some good credits
Earned some good credits
Posts: 122
Joined: Fri Apr 30, 2010 4:52 am
Location: USA
Contact:

Using Threads (pthreads) Within a Class

Post by Deluge »

I don't have a lot of experience with threads so bear with me.

My problem is that I cannot figure out how to process a new thread from a class method. Here is the error ouput from compilation:

Code: Select all

$ g++ test.cpp `wx-config --cxxflags --libs` -o test -pthread
test.cpp: In member function ‘void MainWindow::OnButton(wxCommandEvent&)’:
test.cpp:32: error: argument of type ‘void* (MainWindow::)(void*)’ does not match ‘void* (*)(void*)’
Here is the source:

Code: Select all

#include <iostream>
#include <pthread.h>
#include <wx/wx.h>
using namespace std;

class MainWindow : public wxFrame
{
  public:
    MainWindow(const wxString& title);
    void OnButton(wxCommandEvent& event); // Method that creates the thread
    void *StartThread(void *arg);  // Method that starts the thread
  private:
    wxPanel *bg;
    wxButton *button;
    pthread_t thread1;
    int rc; // thread's return code
};

MainWindow::MainWindow(const wxString& title) : wxFrame(NULL, -1, _T(""))
{
    Center();

    bg = new wxPanel(this, -1);
    button = new wxButton(bg, -1, _T("Start Thread"));

    button->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainWindow::OnButton), 0, this);
}

void MainWindow::OnButton(wxCommandEvent& event)
{
    cout << "Button Pressed" << endl;
    rc = pthread_create(&thread1, NULL, MainWindow::StartThread, (void*)NULL); // Compile error here
    if (rc)
    {
        cout << "Error Occurred" << endl;
        exit(-1);
    }
}

void *MainWindow::StartThread(void *arg)
{
    cout << "This is a thread" << endl;
    wxSleep(3);
    pthread_exit(NULL);
}
I do know how to start the thread using a function outside of the class:

thread handling function:

Code: Select all

void *StartThread(void* arg)
{
    cout << "This is a thread" << endl;
    pthread_exit(NULL);
}
method to create thread:

Code: Select all

void MainWindow::OnButton(wxCommandEvent& event)
{
    cout << "Button Pressed" << endl;
    rc = pthread_create(&thread1, NULL, StartThread, (void*)NULL);
    if (rc)
    {
        cout << "Error Occurred" << endl;
        exit(-1);
    }
}
But I need the thread to have access to the class members, and I don't know how to do that from outside of the "MainWindow" class.
Projects:
Debreate
MyABCs
Stendhal

OSes:
Windows 10 Home (missing my Linux & Freebsd :()
User avatar
doublemax
Moderator
Moderator
Posts: 19114
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Post by doublemax »

The method you pass to pthread_create must be a static method (otherwise, where would the *this* pointer come from?)

You could pass *this* as userdata and use it inside the static method

Code: Select all

rc = pthread_create(&thread1, NULL, MainWindow::StartThread, this);
Be aware that in wxWidgets you *must not* touch any gui elements from secondary threads.
Use the source, Luke!
User avatar
Deluge
Earned some good credits
Earned some good credits
Posts: 122
Joined: Fri Apr 30, 2010 4:52 am
Location: USA
Contact:

Post by Deluge »

doublemax wrote:Be aware that in wxWidgets you *must not* touch any gui elements from secondary threads.
My goal is to be able to disable a wxButton, and wait to re-enable it after a second thread has finished. Can I not re-enable the button from the thread?
Projects:
Debreate
MyABCs
Stendhal

OSes:
Windows 10 Home (missing my Linux & Freebsd :()
User avatar
doublemax
Moderator
Moderator
Posts: 19114
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Post by doublemax »

Deluge wrote:
doublemax wrote:Be aware that in wxWidgets you *must not* touch any gui elements from secondary threads.
My goal is to be able to disable a wxButton, and wait to re-enable it after a second thread has finished. Can I not re-enable the button from the thread?
No. The recommended way is to send a message to the main thread when your secondary thread has finished its task.

You can try to wrap the button enable code between ::wxMutexGuiEnter / ::wxMutexGuiLeave calls, but it's more like a hack and not guaranteed to work.
http://docs.wxwidgets.org/stable/wx_thr ... exguienter
Use the source, Luke!
User avatar
Deluge
Earned some good credits
Earned some good credits
Posts: 122
Joined: Fri Apr 30, 2010 4:52 am
Location: USA
Contact:

Post by Deluge »

Ok, I understand. Thank you. I got some suggestions in another forum and came up with this. Please let me know what you think of it. Have I kept from touching GUI elements in the secondary thread?

Code: Select all

class MainWindow : public wxFrame
{
  public:
    MainWindow(const wxString& title);
    void OnButton(wxCommandEvent& event);
    void EnableButton();
  private:
    static void *StartThread(void *arg);
    wxPanel *bg;
    wxButton *button;
    pthread_t thread1;
    int rc;
};

....

void MainWindow::OnButton(wxCommandEvent& event)
{
    button->Disable(); // Disable button until process is finished
    cout << "Button Pressed" << endl;
    rc = pthread_create(&thread1, NULL, StartThread, this);
    if (rc)
    {
        cout << "Error Occurred" << endl;
        exit(-1);
    }
    else cout << "Thread is OK" << endl;
}

void *MainWindow::StartThread(void *arg)
{
    MainWindow *obj = static_cast<MainWindow*>(arg);
    wxSleep(3);
    cout << "This is a thread" << endl;
    obj->EnableButton();
    pthread_exit(NULL);
}

void MainWindow::EnableButton()
{
    button->Enable();
}
Projects:
Debreate
MyABCs
Stendhal

OSes:
Windows 10 Home (missing my Linux & Freebsd :()
User avatar
doublemax
Moderator
Moderator
Posts: 19114
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Post by doublemax »

Have I kept from touching GUI elements in the secondary thread?
No, the actual button->enable() call happens inside the secondary thread.

If you don't want to rewrite your code, you can try this (not recommended, no guarantee):

Code: Select all

void MainWindow::EnableButton()
{
    ::wxMutexGuiEnter();
    button->Enable();
    ::wxMutexGuiLeave();
}
Otherwise:
http://wiki.wxwidgets.org/Inter-Thread_ ... ain_thread
Use the source, Luke!
Debster
Knows some wx things
Knows some wx things
Posts: 32
Joined: Sat Aug 20, 2005 6:01 pm

Post by Debster »

i use "::wxPostEvent" in the other threads, posting events to the main window which than handles the gui stuff.
User avatar
Deluge
Earned some good credits
Earned some good credits
Posts: 122
Joined: Fri Apr 30, 2010 4:52 am
Location: USA
Contact:

Post by Deluge »

How does this look?

Code: Select all

class MainWindow : public wxFrame
{
  public:
    MainWindow(const wxString& title);
    void OnButton(wxCommandEvent& event);
    void EnableButton(wxCommandEvent& event);
  private:
    static void *StartThread(void *arg);
    wxPanel *bg;
    wxButton *button;
    pthread_t thread1;
    int rc;
};

MainWindow::MainWindow(const wxString& title) : wxFrame(NULL, -1, _T(""))
{
    Center();

    bg = new wxPanel(this, -1);
    button = new wxButton(bg, -1, _T("Start Thread"));

    // Event handler to disable button and start thread
    button->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainWindow::OnButton), 0, this);

    // Event handler to re-enable button
    Connect(0, 0, wxCommandEventHandler(MainWindow::EnableButton), 0, this);
}

void MainWindow::OnButton(wxCommandEvent& event)
{
    button->Disable();
    cout << "Button Pressed" << endl;
    rc = pthread_create(&thread1, NULL, StartThread, this);
    if (rc)
    {
        cout << "Error Occurred" << endl;
        exit(-1);
    }
    else cout << "Thread is OK" << endl;
}

void *MainWindow::StartThread(void *arg)
{
    MainWindow *obj = static_cast<MainWindow*>(arg);
    wxSleep(3); // Give the thread a few seconds to finish
    cout << "This is a thread" << endl;
    wxCommandEvent nullevent(0, 0); // Create an event to pass to main thread
    wxPostEvent(obj, nullevent); // Pass null event
    pthread_exit(NULL);
}
Projects:
Debreate
MyABCs
Stendhal

OSes:
Windows 10 Home (missing my Linux & Freebsd :()
Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Post by Auria »

This looks good to me!
"Keyboard not detected. Press F1 to continue"
-- Windows
User avatar
doublemax
Moderator
Moderator
Posts: 19114
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Post by doublemax »

Looks ok, but you should use a user-defined id for the event, not 0.

And for clarity reasons, i would cast the pointer to wxEvtHandler (and to make sure that you don't call methods on it that you shouldn't):

Code: Select all

wxEvtHandler *obj = wxDynamicCast(arg, wxEvtHandler);
if(obj) {
  // ...
} 
Use the source, Luke!
User avatar
Deluge
Earned some good credits
Earned some good credits
Posts: 122
Joined: Fri Apr 30, 2010 4:52 am
Location: USA
Contact:

Post by Deluge »

Like this?

Code: Select all

const int ID_NULL = wxNewId();

Code: Select all

Connect(ID_NULL, 0, wxCommandEventHandler(MainWindow::EnableButton), 0, this);

Code: Select all

void *MainWindow::StartThread(void *arg)
{
    wxEvtHandler *obj = wxDynamicCast(arg, wxEvtHandler);
    if (obj)
    {
        wxSleep(3); // Give the thread a few seconds to finish
        cout << "This is a thread" << endl;
        wxCommandEvent nullevent(0, ID_NULL); // Create an event to pass to main thread
        wxPostEvent(obj, nullevent); // Pass null event
    }
    pthread_exit(NULL);
}
Projects:
Debreate
MyABCs
Stendhal

OSes:
Windows 10 Home (missing my Linux & Freebsd :()
User avatar
doublemax
Moderator
Moderator
Posts: 19114
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Post by doublemax »

My mistake, i shouldn't have called it "id", it's a little ambiguous here. I meant "event type", the id usually refers to the id of the object (e.g. clicked button etc), which you don't have in this case.

So you should use a custom event type and wxID_ANY as id.

Code: Select all

Connect(ID_NULL, wxCommandEventHandler(MainWindow::EnableButton), 0, this);
or
Connect(wxID_ANY, ID_NULL, wxCommandEventHandler(MainWindow::EnableButton), 0, this);

Code: Select all

wxCommandEvent nullevent(wxID_ANY, ID_NULL);
Use the source, Luke!
User avatar
Deluge
Earned some good credits
Earned some good credits
Posts: 122
Joined: Fri Apr 30, 2010 4:52 am
Location: USA
Contact:

Post by Deluge »

doublemax wrote:My mistake, i shouldn't have called it "id", it's a little ambiguous here. I meant "event type", the id usually refers to the id of the object (e.g. clicked button etc), which you don't have in this case.

So you should use a custom event type and wxID_ANY as id.

Code: Select all

Connect(ID_NULL, wxCommandEventHandler(MainWindow::EnableButton), 0, this);
or
Connect(wxID_ANY, ID_NULL, wxCommandEventHandler(MainWindow::EnableButton), 0, this);

Code: Select all

wxCommandEvent nullevent(wxID_ANY, ID_NULL);
When I do it like that, MainWindow::EnableButton() doesn't get called. It appears to work fine with:

Code: Select all

Connect(ID_NULL, 0, wxCommandEventHandler(MainWindow::EnableButton), 0, this);
...
wxCommandEvent nullevent(0, ID_NULL);
Here is the entire code if you want to check it out:

Code: Select all

#include <iostream>
#include <pthread.h>
#include <wx/wx.h>
#include <wx/sound.h>
using namespace std;

const int ID_NULL = wxNewId();

class MainWindow : public wxFrame
{
  public:
    MainWindow(const wxString& title);
    void OnButton(wxCommandEvent& event);
    void EnableButton(wxCommandEvent& event);
  private:
    static void *StartThread(void *arg);
    wxPanel *bg;
    wxButton *button;
    wxString *cur_sound;
    pthread_t thread1;
    int rc;
};

MainWindow::MainWindow(const wxString& title) : wxFrame(NULL, -1, _T(""))
{
    Center();

    bg = new wxPanel(this, -1);
    bg->SetFocus();
    button = new wxButton(bg, -1, _T("Start Thread"));
    cur_sound = new wxString();

    // Event handler to disable button and start thread
    button->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainWindow::OnButton), 0, this);

    // Event handler to re-enable button
    Connect(ID_NULL, 0, wxCommandEventHandler(MainWindow::EnableButton), 0, this);
}

void MainWindow::OnButton(wxCommandEvent& event)
{
    button->Disable();
    cout << "Button Pressed" << endl;
    cur_sound->Clear();
    cur_sound->Append(_T("dog.wav"));
    wxSound dog(*cur_sound);
    dog.Play(wxSOUND_SYNC);
    rc = pthread_create(&thread1, NULL, StartThread, this);
    if (rc)
    {
        cout << "Error Occurred" << endl;
        exit(-1);
    }
    else cout << "Thread is OK" << endl;
    bg->SetFocusIgnoringChildren();
}

void *MainWindow::StartThread(void *arg)
{
    wxEvtHandler *obj = wxDynamicCast(arg, wxEvtHandler);
    if (obj)
    {
        cout << "Playing Sound" << endl;
        wxCommandEvent nullevent(0, ID_NULL); // Create an event to pass to main thread
        wxPostEvent(obj, nullevent); // Pass null event
    }
    pthread_exit(NULL);
}

void MainWindow::EnableButton(wxCommandEvent& event)
{
    button->Enable();
}

class App : public wxApp
{
  public:
    virtual bool OnInit();
};

IMPLEMENT_APP(App)

bool App::OnInit()
{
    MainWindow *frame = new MainWindow(_T("Testing Threads"));
    frame->Show();
    SetTopWindow(frame);
    return true;
}
Projects:
Debreate
MyABCs
Stendhal

OSes:
Windows 10 Home (missing my Linux & Freebsd :()
User avatar
doublemax
Moderator
Moderator
Posts: 19114
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Post by doublemax »

Sorry for the confusion, i mixed up id and event type in the wxCommandEvent ctor, it should read like this:

Code: Select all

wxCommandEvent nullevent(ID_NULL, wxID_ANY);
Use the source, Luke!
User avatar
Deluge
Earned some good credits
Earned some good credits
Posts: 122
Joined: Fri Apr 30, 2010 4:52 am
Location: USA
Contact:

Post by Deluge »

Thanks, that works.
Projects:
Debreate
MyABCs
Stendhal

OSes:
Windows 10 Home (missing my Linux & Freebsd :()
Post Reply