winapinim-lang

Window becomes nonresponsive and freezes with move attempt during long running process


The language in question is Nim, using the wNim library, but really the question is about the win32 api, since wNim is a very lightweight wrapper over Win32 gui.

I have a single threaded program. When the user clicks the button it launches a long-running calculation. On every nth cycle the calculation occasionally updates the window with InvalidateRect, followed by UpdateWindow. The WM_PAINT handler draws some stuff on the screen right away. Sometimes I introduce a sleep for a few ms thinking this will give the system WM_xxx messages some time to catch up.

This normally works fine. I can watch the calculation progress graphically in the client area.

However when I try to move the window during this time, (with or without the sleep) while the thing is doing its updating, not only does it not respond to the move (which is understandable if sleep(...) is omitted), but the graphics from the onPaint stop completely and the window turns that ghostly white color that Windows does when the program dies. Usually the debug output in the cli continues anyway. Often, but not always, the cli output stops and the Windows dialog comes up telling me the program crashed. At any time I'm able to hit ctrl-c in the cli and get back to normal. Can someone explain what particular sequence of events and mishandled messages is causing everything to die an ugly death?

Below is the pseudocode. Is there a proper way to allow a long-running foreground computation to update the client area while also responding to other user events, without multi-threading? Or is multithreading the best option here? I suspect my sleep(...) call is the place to yield some cycles back to the messaging mechanism.

Pseudocode:

proc onButton():
    while(stuffNotDone):
        doExpensiveStuff()
        InvalidateRect(false) # <-- Win32 call
        UpdateWindow()        # <-- Win32 call
        Sleep(10)             # <-- Win32 call (optional)

proc onPaint(event):
    # Responds to WM_PAINT
    var dc = PaintDC(...)           # <-- leads to Win32 BeginPaint
    dc.blit(...)                    # <-- leads to Win32 call
    SendMessage(hwnd, WM_APP+3,0,0) # <-- Win32 call to clear data elsewhere
    

Solution

  • If you read SendMessage's documentation, you'll find, that it SendMessage won't return until event is finished being processed. In some cases it my lead to recursion if that would cause invalidation or very long chain of handler calls, if processing the message results in other events being triggered in same window. To allow delayed processing use PostMessage.

    UpdateWindow bypasses queue. It essentially calls Window procedure directly with paint event. The same window procedure which called this handler. Sleep in handler happens after the paint complete and it stops event processing.

    Foreground processing simply isn't done in Windows GUI. It's a synchronous loop of processing event for each available Window in one or more threads (in comparison, most platforms and multiple GUI frameworks allow that to be done only in main thread). Delayed asynchronous events are created using timers.

    To process data without pausing Window procedure and event loop you must have a worker thread, a paradigm known since 1990s. IIRC Nim facilitates async processing. and easier creationof threads, I suppose you have to check there.