1

I have an event-handling thread class which allows me to raise events from other threads without interrupting their operation. When the destructor is called I send a quit message to the thread, but its message loop doesn't seem to receive this message.

#include <iostream>
using namespace std;

#include <windows.h>
#define WIN32_LEAN_AND_MEAN

#define ET_EVENTPUSH 7854
#define ET_QUITLOOP 6581
#define EVENT_HANDLER void
typedef void (*EVENT)(LPVOID);

//# event handler thread
class event_thread
{
private:
    //vars
    HANDLE  m_thread;   //event loop thread
    DWORD   m_id;       //thread id

    //thread procedure
    static DWORD WINAPI listener_loop(LPVOID);

public:
    //constructor
    event_thread();
    ~event_thread();

    //functions
    void push_event(EVENT, LPVOID);
};

//# event handler thread
event_thread::event_thread()
{
    //create thread
    m_thread = CreateThread(NULL, 0, listener_loop, NULL, 0, &m_id); 
}
event_thread::~event_thread()
{
    DWORD dw;

    //stop thread
    if (m_thread)
    {
        dw = GetLastError(); 
        PostThreadMessage(m_id, ET_QUITLOOP, 0, 0);
        dw = GetLastError(); 
        WaitForSingleObject(m_thread, INFINITE);
        CloseHandle(m_thread);
    }
}

void event_thread::push_event(EVENT e, LPVOID p)
{
    PostThreadMessage(m_id, ET_EVENTPUSH, (UINT)e, (UINT)p);
}
DWORD WINAPI event_thread::listener_loop(LPVOID param)
{
    MSG     msg;
    #define event_type  msg.message
    #define event_call  ((EVENT)(msg.wParam))
    #define event_param (LPVOID)(msg.lParam)

    //while thread is alive
    while (GetMessage(&msg, NULL, 0, 0))
    {
        switch (event_type)
        {
        case ET_EVENTPUSH:
            event_call(event_param);
            break;
        case ET_QUITLOOP:
            return 0;
        }
    }
    return 0;
}

EVENT_HANDLER asda(LPVOID as)
{
    cout << (int)as << endl;
}

int main()
{
    event_thread thr;

    int i = 1;
    while (!GetAsyncKeyState(VK_NUMPAD1))
    {
        thr.push_event(asda, (LPVOID)(i++));
        Sleep(20);
    }

    return 0;
}

GetMessage doesn't return when I send a quit message. And using GetLastError() after the PostThreadMessage call gives 1444 ERROR_INVALID_THREAD_ID.

I don't want to use PeekMessage because it uses 100% CPU, nor Sleep 'cos it's apparently bad practice.

Thanks

  • 2
    I don't see where you post ET_QUITLOOP. Usually such things are done using PostQuitMessage, but you don't use it as well. Possibly in `~event_thread` you wanted to post ET_QUITLOOP. – Alex F Mar 15 '14 at 18:55
  • @alexfarber yeah sorry man i did mean to do that (posted an older version of the code) ... but even that doesn't help - WaitForSingleObject still never returns –  Mar 15 '14 at 22:02
  • I tried to read this but the macros are just too much. That's not how to write a program. – David Heffernan Mar 15 '14 at 22:31
  • @DavidHeffernan sorry, i just didn't want something as ugly as `((EVENT)(msg.wParam))((LPVOID)(msg.lParam))` in my code –  Mar 16 '14 at 10:38

1 Answers1

3

ERROR_INVALID_THREAD_ID means you tried to post a message to a thread ID that does not have a message queue. So either you tried to post before the thread message loop was running, or after the thread had already terminated.

When your thread starts running, it should post a message to itself first to create a message queue, then set a signal to indicate that it is ready to receive messages, then finally enter its message loop. In your class constructor, after calling CreateThread(), it can wait for that signal before then exiting.

Try this:

#include <iostream>
using namespace std;

#include <windows.h>
#define WIN32_LEAN_AND_MEAN

#define ET_START WM_USER+1
#define ET_EVENTPUSH WM_USER+2
#define ET_QUITLOOP WM_USER+3

#define EVENT_HANDLER void
typedef void (*EVENT)(LPVOID);

//# event handler thread
class event_thread
{
private:
    //vars
    HANDLE  m_thread;   //event loop thread
    DWORD   m_id;       //thread id
    bool    m_ready;    // message queue is ready

    //thread procedure
    static DWORD WINAPI listener_loop(LPVOID);

public:
    //constructor
    event_thread();
    ~event_thread();

    //functions
    bool push_event(EVENT, LPVOID);
};

//# event handler thread
event_thread::event_thread()
{
    //create thread
    m_ready = false;
    m_thread = CreateThread(NULL, 0, &listener_loop, this, 0, &m_id); 
    if (m_thread)
    {
        while (!m_ready)
            Sleep(10);
    } 
}
event_thread::~event_thread()
{
    //stop thread
    if (m_thread)
    {
        if (!PostThreadMessage(m_id, ET_QUITLOOP, 0, 0))
        {
            DWORD dw = GetLastError(); 
            ...
        } 
        WaitForSingleObject(m_thread, INFINITE);
        CloseHandle(m_thread);
    }
}

bool event_thread::push_event(EVENT e, LPVOID p)
{
    return PostThreadMessage(m_id, ET_EVENTPUSH, (WPARAM)e, (LPARAM)p);
}

DWORD WINAPI event_thread::listener_loop(LPVOID param)
{
    event_thread *pThis = (event_thread*)param;

    MSG     msg;
    #define event_type  msg.message
    #define event_call  ((EVENT)(msg.wParam))
    #define event_param (LPVOID)(msg.lParam)

    PostThreadMessage(pThis->m_id, ET_START, 0, 0);
    pThis->m_ready = true;

    //while thread is alive
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        switch (event_type)
        {
        case ET_EVENTPUSH:
            event_call(event_param);
            break;
        case ET_QUITLOOP:
            PostQuitMessage(0);
            break;
        }
    }
    return 0;
}

If you do not like the Sleep() loop you can replace m_ready with a waitable event, and then use SetEvent() and WaitForSingleObject().

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I did that in a previous implementation and that worked fine (I used my own queue structure), but I just thought that since Windows makes its own message queue I'd give that a go... thanks still –  Mar 16 '14 at 10:36
  • You can certainly use a thread's native message queue, just keep in mind that a thread does not create its message queue until the first time it calls any user32 function, a new thread does not have a message queue by default, so better to call a user32 function earlier before the message loop starts running so you are in more control over when the queue is created, and then use a signal to know when the queue is available. – Remy Lebeau Mar 16 '14 at 17:36
  • Well the problem I have _only_ occurs when the thread destructor is called at the end of `int main()` (after `return 0` is executed). If I create a thread class dynamically and destroy it before `return 0` then the code works perfectly well. –  Mar 16 '14 at 18:52