Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Executing a method on ui thread due to an event on background thread

I've got a background thread that is polling a server. When there's data, I want to handle the data on the UI thread. If I store the hwnd of the main window.

How can I get a particular method static void DataHandler(void* data) to be executed on the UI thread?

I think creating a timer passing the hwnd and the function pointer would work. But is there a better way? Can I use PostMessage to somehow get the datahandler invoked.

Also, I'm not writing the UI code, so I don't have the ability to modify anything in the message loop.

like image 877
RP4 Avatar asked Nov 28 '22 23:11

RP4


1 Answers

There are two main methods I use to communicate between threads most often.

1) PostMessage()

Create a custom windows message, ala:

#define WM_YOU_HANVE_DATA WM_USER + 101

Create a custom datatype that will hold the data you want to send to the main thread for processing:

struct MyData
{
  string client_;
  string message_type_;
  string payload_;
};

From you worker thread, instantiate a copy of MyData on the heap, populate it, and send it off to the main thread:

MyData* data = new MyData;
data->client_ = "hoser";
// ... etc
PostMessage(main_wnd_handle, WM_YOU_HAVE_DATA, reinterpret_cast<WPARAM>(data), );

In the main thread, handle this message and process the data in whatever way appropriate.

BEGIN_MESSAGE_MAP(MyAppWindow, CDialogEx)
        // ...  stuff's going to already be here
        ON_MESSAGE(WM_YOU_HAVE_DATA, OnYouHaveData)
END_MESSAGE_MAP()

// ...

An important note: MyAppWindow's main thread now owns the memory pointed to by the MyData*, so you have to take ownership of it. I do this with auto_ptr here:

LRESULT MyAppWindow::OnYouHaveData(WPARAM wp, LPARAM )
{
  auto_ptr<MyData> data(reinterpret_cast<MyData*>(wp));
  DisplayeClient(data->client_);
  // etc
  return 0;  
}

This is probably the easiest method that is also robust in the sense that it is thread safe. Because you pass ownership of the data to the main thread, there is no contention.

The biggest downfall of this approach is limitations in scale. This relies on the Windows message pump to move data between threads. Almost always, this is not an issue. But there is a limit to the number of messages the Windows message queue can handle:

There is a limit of 10,000 posted messages per message queue.

(reference)

Again, for most applications this is no problem.

2) QueueUserAPC()

An asynchronous procedure call (APC) is a function that executes asynchronously in the context of a particular thread. (Link) If there is a function ProcessIncomingData() you want to be executed on the main thread, but you want to trigger it from a worker thread, you can call that function in a fairly direct way using QueueUserAPC().

As with the PostMessage() method, you start with a custom datatype that you instantiate on the heap:

struct MyData
{
  string client_;
  string message_type_;
  string payload_;
};

// ...

MyData* data = new MyData;
data->client_ = "hoser";

Define a user APC, remembering to take ownership of the incoming data:

VOID CALLBACK ProcessIncomingData(ULONG_PTR in)
{
  auto_ptr<MyData> data(reinterpret_cast<MyData*>(in));
  // magic happens
}

Then you queue up the asynch procedure call. With the PostMessage() method, you needed the main thread's window HWND. Here, you need the main thread's actual thread HANDLE.

HANDLE main_thread = my_thread_params.main_thread_handle_;
QueueUserAPC(ProcessIncomingData, main_thread, reinterpret_cast<ULONG_PTR>(data));

There's one BIG caveat. In order for your APC to be called by the main thread, the main thread must be in an alertable wait state. You enter an alertable wait state when you call one of the WaitEx() functions such as WaitForMultipleObjectsEx() with the "alertable" flag set to true.

The problem is GUI threads almost never should be in an alertable wait state, because you should almost never be waiting. Waiting in the main thread will block the message pump, making your application seem to freeze. This is very bad. I include this method for completeness -- you often need to communicate between two worker (non-GUI) threads, and this is often the most efficient way to do it.

like image 196
John Dibling Avatar answered Dec 05 '22 00:12

John Dibling