0

I'm building a real time graphics application and I've noticed that under certain conditions the operating system will post nonqueued messages that block my program. For example, this will happen during the entire time that a user is resizing the window.

To solve this problem, I would like to put the window on a different thread, but I read that this is a terrible idea because of the way windows messages work. Is it still a problem if I process all messages on the other thread, and never send or receive messages from the main thread? I'm thinking about setting up my own message queue with a mutex so I can pass the data I care about back and forth. If this won't work, are there any other ways to solve this problem?

Here's a simplified version of the code I'm running:

LRESULT CALLBACK WindowCallback(HWND window, UINT message, WPARAM wParam, LPARAM lParam) {
    if (message == WM_SIZE) {
        // This event gets called a lot
        // Handle window resize
        return 0;
    } else {
        return DefWindowProc(window, message, wParam, lParam);
    }
}

int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prev, LPSTR cmd, int numCmd) {
    WNDCLASSA class = {};
    class.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    class.lpfnWndProc = WindowCallback;
    class.lpszClassName = "Name";
    class.hInstance = instance;

    RegisterClassA(&class)l
    HWND win = CreateWindowExA(0, class.lpszClassName, "Name",
                               WS_VISIBLE | WS_OVERLAPPEDWINDOW,
                               CW_USEDEFAULT, CW_USEDEFAULT, 600, 400,
                               0, 0, instance, 0);
    while(true) {
        // Handle Events
        MSG msg = {};
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) {
            if (msg.message == WM_QUIT)
                break;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        // Do Updating of State and Graphics Rendering Here
    }
}

Thanks in advance!

  • The system posts messages to the thread that created the window, so you'd be just shifting the problem to a different thread, plus introducing new problems by running multiple message loops. `messages that block my program` Messages don't block a program by themselves, it's up to your code how to handle them. – dxiv Jan 08 '21 at 06:53
  • If I understand it correctly, when the operating system wants to send a nonqueued message, it will call my window callback directly, without adding it to the message queue. I may be using the wrong terminology, but the blocking that I'm talking about occurs when the OS sends many messages successively. I'm okay with the other thread being "blocked" though. My thoughts on this is that if the other thread gets many messages, they will post them to my own queue, and the main thread can choose to process only a few of them, and wait until the next frame to process more. – labmonkey398 Jan 08 '21 at 06:58
  • @labmonkey398 while it is possible to *create* a window in a non-main thread and run a message loop for it in that thread, that is generally not advisable to do. And you can’t *move* a window to another thread. The main thread should handle the UI, and non-main threads should handle work that won’t block the UI and vice versa. You can’t process window messages across thread boundaries. And modal message loops, like resizing a window, shouldn’t be blocking your app if you coded things properly. – Remy Lebeau Jan 08 '21 at 07:11
  • @labmonkey398 It would be best if you posted some code to showcase the problem you are trying to solve. Sent messages are not queued, indeed, but it's still the same wndProc code that handles them (with additional complications if they were sent from a different thread or process). The rule of thumb is to keep all UI in the main thread, and offload any seizable processing to other non-UI threads. – dxiv Jan 08 '21 at 07:13
  • I just updated with some code. It's very possible that I'm handling the messages wrong because this is my first time using the API. I'm basically trying to mimic some of the functionality of a standard windowing library like GLFW. – labmonkey398 Jan 08 '21 at 07:30
  • @labmonkey398 You normally want to process `WM_SIZE` in order to update the UI in sync. But it's not obvious from the snippet why it would get "*called a lot*", unless the user moves/resizes the window all the time, or there is code elsewhere that does it. See also [Win32: My Application freezes while the user resizes the window](https://stackoverflow.com/questions/3102074/win32-my-application-freezes-while-the-user-resizes-the-window). – dxiv Jan 08 '21 at 08:16
  • @dxiv What do you mean by 'process'? Am I supposed to handle it somewhere other than the callback? And yes, it's the user. If the user grabs the bottom right corner and moves it, the graphics that I'm drawing start to get rendered in a weird way, and I'd like to be able to draw them correctly while the user is resizing the window. It seems that from what both of you are saying, it will be a bad idea to move the window to another thread, even though events won't be passed in between. Is this kind of situation usually handled by moving the update and rendering code to another thread instead? – labmonkey398 Jan 08 '21 at 08:25
  • You have misdiagnosed your problem. Your problem is nothing to do with threading and blocking. It's perfectly normal to be able to draw whilst resizing for a window in the main thread. – David Heffernan Jan 08 '21 at 08:37
  • @DavidHeffernan I agree that the problem has nothing to do with that, but I thought that threading could be a possible solution. Do you know how I can change my current control flow so that I can draw while it's resizing? Because currently, as soon as the user starts to resize it, the main thread enters a loop that continues until the user stops resizing the window. – labmonkey398 Jan 08 '21 at 08:47
  • @labmonkey398 In the end you have to get the UI updated, and you won't solve that with worker threads. If the bottleneck is redrawing then don't (do a full) redraw during interactive resize. Use [`WM_ENTERSIZEMOVE`](https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-entersizemove) / [`WM_EXITSIZEMOVE`](https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-exitsizemove) to know when that happens. – dxiv Jan 08 '21 at 08:49
  • @dxiv I just saw the link that you added above, and that's actually the same problem as mine! Modal operations make a lot more sense now, so thanks. I'll try and listen to those two messages or do one of the solutions in the other question. If you put those into an answer, then I'll accept it, unless you think it would be better to mark this as a duplicate of the other link. – labmonkey398 Jan 08 '21 at 09:07
  • @dav While it's possible to render during a resize operation, it's the modal size/move message loop that gets in the way. The code posted uses a non-blocking message loop, that gets preempted whenever the system spins up a modal message loop. Moving the non-blocking message loop onto a different thread would indeed solve *that* issue, at the expense of introducing near impossible to solve issues. – IInspectable Jan 08 '21 at 09:47
  • @lab I believe the system also enters a modal message loop whenever the user opens a window's menu. You'd have to account for that as well. – IInspectable Jan 08 '21 at 09:52
  • @IInspectable What issues will it introduce? Based on other SO posts and the docs, it seems that the main issues deal with interactions between threads and missing messages that end up freezing the window. But I don't see how that's possible if I don't open another window or handle windows messages on the main thread. Am I missing something? – labmonkey398 Jan 08 '21 at 09:58
  • @lab Reentrancy is frequently overlooked. A window procedure needs to be prepared to be called while it is in the middle of handling a message. That makes synchronization between threads a lot harder. None of that behavior is terribly well documented, which puts you in a situation where you have to synchronize threads with unknown call graph characteristics. – IInspectable Jan 08 '21 at 10:14
  • @labmonkey398 `WM_SIZE` is not sent to a window until after the resize is finished. If you want to keep drawing while the window is being resized, try handling `WM_SIZING` to get the window's updated size so you can update your state info accordingly, so that subsequent `WM_PAINT` messages can draw the window correctly. – Remy Lebeau Jan 08 '21 at 18:09
  • Hey All, thank you for your comments; they were extremely helpful! I decided to move my update and rendering logic into the `WM_PAINT` case in the callback function. Now, I just call `RedrawWindow()` in the main loop (thanks @remy [Repaint Message](https://stackoverflow.com/a/22444044/7623056)). This has solved the issue for now, and if I run into performance issues in the future, I may look at using a timer to spread one update and render call across multiple "frames". Thanks for steering me away from threading. I didn't fully understand the drawbacks. – labmonkey398 Jan 08 '21 at 19:22

0 Answers0