开发者

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

开发者 https://www.devze.com 2023-01-17 22:09 出处:网络
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.

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(vo开发者_运维百科id* 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.


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.


One thing that you could do - use an inter-thread signalling object perhaps as simple as a boolean flag. When data appears on the server polling thread, you can signal the flag. You could check for this flag in the message loop of your UI thread. Alternatively, you could just send the UI thread a custom window message.

Now that I re-read your question - since you can't change the UI code, this approach wouldn't work. You could use the WIN32 API to add your own custom message hook function to fix this problem.


The method I use (which requires modification to the UI thread, but I think all methods would in some respect) is to define a custom message ID, which is handled in the UI thread. The message format and handling is encapsulated in a class, so the main UI thread need only forward messages to the class handler unchanged, without knowing what the particular format is.

After that, it's a simple matter of encapsulating an arbitrary function call (I use a dynamically allocated boost::function object, but there are other options), pass it to the main window thread with the custom message ID, and the handler should get called in the context of the main thread. Creating custom message types in win32? has details on the custom message part, as you noted; just make sure whatever data/object you pass is allocated on the heap and freed inside the handler function if it's going to be an asynchronous call (eg: PostMessage).

Update: What John said in option 1, except write the handler operation in a function which is called by the handler in the UI, so you have minimal impact on the UI code, and if you change the data structure you're passing in any way, you do not need to update the UI code. That's my only additional suggestion.

Hope that helps.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号