We added a thread to keep the GUI responsive. Then we broke it.

By Stephen Kellett
15 May, 2026

Memory Validator is a multi-threaded application. The main thread runs the user interface. If the main thread blocks, the UI becomes unresponsive — and if it stays unresponsive long enough, users assume the software has hung.

The standard solution to problems like this is to do the work onto dedicated worker threads. MV’s timeline view does exactly this: a dedicated thread periodically samples memory use and posts a refresh event to the GUI thread when new data is ready. The GUI thread processes the refresh and updates the display.

Avoiding a deadlock

The catch is how you post that event.

From a worker thread you must use PostMessage(), not SendMessage().

Using SendMessage() from a non-GUI thread will cause a deadlock.

What went wrong

Under certain settings and workloads the timeline thread was generating data faster than the GUI thread could process refresh events. The thread kept posting update events. The message queue kept filling with events to process. Eventually the GUI thread was spending all its time processing refresh events and nothing else — including user input. The UI locked completely.

The irony is hard to miss. We added a dedicated worker thread specifically to keep the GUI thread free and responsive. Then we made the GUI thread unresponsive by sending it too many events.

The fix

Two changes.

Before posting a refresh event, check whether one is already pending. If a refresh is already queued, there is no point adding another — the queued one will handle it:

if (!m_bRefreshQueued)
{
    m_bRefreshQueued = true;
    PostMessage(WM_REFRESH_TIMELINE, 0, 0);
}

// In the message handler:

void OnRefreshTimeline()
{
    m_bRefreshQueued = false;
    // ... update the timeline view
}

The second change: check the session state. If the session is in standby — the monitored process has ended, data collection is finished — there is nothing to refresh. Skipping the event in that case cleaned up the post-session screen behaviour:

if (m_session.IsStandby())
    return;

if (!m_bRefreshQueued)
{
    m_bRefreshQueued = true;
    PostMessage(WM_REFRESH_TIMELINE, 0, 0);
}

There was no need to change how we posted the events. We kept the PostMessage() API call.

What to take away

If you have a worker thread posting UI updates, add a pending flag before each PostMessage() call.

Check session or application state too — there is no point refreshing a view that has nothing new to show.

Fully functional, free for 30 days