سؤال

I have an operation which ends in about 20 seconds. To avoid freezing, I want to create a thread and update a label text in it every second. I searched a lot, since everyone has different opinion, I couldn't decide which method to use.

I tried SendMessage and it works but some people believe that using SendMessage is not safe and I should use PostMessage instead. But PostMessage fails with ERROR_MESSAGE_SYNC_ONLY (1159).

char text[20] = "test text";
SendMessage(label_hwnd, WM_SETTEXT, NULL, text);

I searched about this and I think it's because of using pointers in PostMessage which is not allowed. That's why it fails.

So, what should I do? I'm confused. What do you suggest? Is this method is good for change UI elements in other thread?

Thanks

هل كانت مفيدة؟

المحلول

The documentation for ERROR_MESSAGE_SYNC_ONLY says:

The message can be used only with synchronous operations.

This means that you can use synchronous message delivery, i.e. SendMessage and similar, but you cannot use asynchronous message delivery, i.e. PostMessage.

The reason is that WM_SETTEXT is a message whose parameters include a reference. The parameters cannot be copied by value. If you could deliver WM_SETTEXT asynchronously then how would the system guarantee that the pointer that the recipient window received was still valid?

So the system simply rejects your attempt to send this message, and indeed any other message that has parameters that are references.

It is reasonable for you to use SendMessage here. That will certainly work.

However, you are forcing your worker thread to block on the UI. It may take the UI some time to update the caption's text. The alternative is to post a custom message to the UI thread that instructs the UI thread to update the UI. Then your worker thread thread can continue its tasks and let the UI thread update in parallel, without blocking the worker thread.

In order for that to work you need a way for the UI thread to get the progress information from the worker thread. If the progress is as simple as a percentage then all you need to do is have the worker thread write to, and the UI thread read from, a shared variable.

نصائح أخرى

Well, the error says it all. The message cannot be sent asynchronously. The thing about PostMessage is that it posts the message to the listening thread's queue and returns immediately, without waiting for the result of message processing. SendMessage on the other hand, waits until the window procedure finishes processing the message and only then it returns.

The risk of using PostMessage in your case is that before window procedure processes the message you may have deallocated the string buffer. So it is safer to use SendMessage in this instance and that's what MS developers probably thought about when they decided not to allow asynchronous posting of this particular message.

EDIT: Just to be clear, of course this doesn't eliminate the risk of passing a naked pointer totally.

From MSDN

If you send a message in the range below WM_USER to the asynchronous message functions (PostMessage, SendNotifyMessage, and SendMessageCallback), its message parameters cannot include pointers. Otherwise, the operation will fail.

The asynch PostMessage() alternative requires that the lifetime of the data passed in the parameters is extended beyond the message originator function. The 'classic' way of doing that is to heap-allocate the data, PostMessage a pointer to it, handle the data in the message-handler in the usual way and then delete it, (or handle it in some other way such that it does not leak). In other words, 'fire and forget' - you must not touch the data in the originating thread after the PostMessage has been issued.

The upside is that PostMessage() allows the originating thread to run on 'immediately' and so do further work, (maybe posting more messages). SendMessage() and such synchronous comms can get held up if the GUI is busy, imacting overall throughput.

The downside is that a thread may generate mesages faster than the GUI can process them. This usually manifests to the by laggy GUI responses, especially when performing GUI-intenisve work like moving/resizing windows and updating TreeViews. Eventually, the PostMessage call will fail when 10,000+ messages are queued up. If this is found to be a problem, additional flow-control may have to be added, so further complicating the comms, ( I usually do that by using a fixed-size object pool to block/throttle the originating thread if all available objects are stuck 'in transit' in posted, but unhandled, messages.

I think you can use SendMessage safely here. Then you don't need to worry about memory persistence for your string and other issues. SendMessage is not safe when you send messages from another message handler or send message to blocked GUI thread, but if in your case you know it is safe - just use it

This is not a problem with the PostMessagebut a problem with the message you are sending - WM_SETTEXT. First a common misconception is that if you SendMessage() to a control from a thread, it is different from calling GUI API, it is in fact NOT. When you call a GUI API (from anywhere) for example to set text, windows implement this in the form of SendMessage() call. So when you are sending the same message, it is essentially same as calling the API. Although directly GUI access like this works in many ways it is not recommended. For this reason, I would beg to disagree with the accepted answer by @David.

The correct way is (code on the fly)

char* text = new char[20] 
strcpy_s(text, "test text");
PostMessage(label_hwnd, IDM_MY_MSG_UPDATE_TEXT, NULL, text); 

you will updated the text in your own message IDM_MY_MSG_UPDATE_TEXT handler function and delete the memory.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top