'Multiple windows with seperate threads stop working since Windows 10 20H1

I have written an application with a main window that opens multiple sub windows as child windows (WS_CHILD) - i.e. a statusline, a bar with buttons and a content part. The main window so far is a frame for the others and acts in background. It is also organizing messages and many more. The content part can be replaced by other windows with other content. Every window has its own thread that works some kind of programming language that fills the window with its content. To speed up the change of the program, I use a thread pool. So if a window is closed, it releases the thread to suspend mode and the next window that is opened can use this thread again.

So far, so good. This construction works fine for more than 10 years and is the base for several programs.

Now with the latest upgrade of Windows 10 20H1 the program stopped working. It looks like the message queue within windows is working for a while and then hangs up in DispatchMessage after some seconds. Some windows do not receive any more messages in the WndProc although the underlying thread is just waiting for work. The fastest method to reach this, is to open a window not with WS_CHILD but with WS_POPUP. Then the window occurs but it does not receive a paint message and never get filled. It is just the frame After this, the program does not act any longer.

I have tried to find PostMessage and SendMessage that is sent from one thread to another. There had been a view, but they had been eliminated now (and did not make any problem in the past).

Another idea was to remove the thread pool and use a new thread for every new window. But this did not make anything better. The only thing that helped was to keep open all created windows. As the programs often run the whole day or even several days this is no solution either. I cannot keep some hundreds of windows in background.

Any ideas what could be the problem? Or does someone have similar experiences with the 20H1 upgrade of windows?

Unfortunately the programs on this base are running on many computers all over the world and some customers already made the upgrade of Windows. The quick solution was to roll back the upgrade but this cannot be the eternal solution of course.

EDIT: Here I have a sample for the problem:

SampleThreadWindow.cpp

// What the program does:
// It creates a main-window in the main-thread and then creates seperate threads 
// and a child-window within this thread. Randomly one of the child windows is destroyed.
// If this is the case, the thread for the window is suspended.
// By timer, the thread is resumed and creates a new window.
// To reproduce the problem: Wait until one of the windows had been
// destroyed and then show the about-dialog. Sometimes the
// program hangs when starting the dialog, the other times it stops when
// ending it. In rare cases you have to call the dialog twice.
// This behaviour shows on Windows 10 2004, only, but not on Windows 10 1909.


// Default libraries
#include <windows.h>
#include <time.h>
#include "SampleThreadWindow.h"

// Class-name
#define  WC_MAINWINDOW     TEXT("MainWindowClass")
#define  WC_CHILDWINDOW    TEXT("ChildWindowClass")
#define  MAIN_TITLE        TEXT("Main Window")
#define  CHILD_TITLE       TEXT("Child Window")

// set TRUE if threads for all child-windows should end
volatile BOOL   fEndChildWindowThread = FALSE;

// Reminder for the main window
HWND hWndMain = NULL;

// Instance of the process
HINSTANCE hInstanceMain = NULL;

// Close all windows on exit
BOOL fCloseAll = FALSE;

// the state of a thread in the pool
enum eThreadState
{
   Unused,     // unused and uninitialized thread
   Active,     // active thread bound to a window
   Suspended,  // suspended thread that had been active but the attached window was closed
   Finished    // finished thread (at program end)
};

// structure for a thread in the thread-pool
struct CThreadPool
{
   // Handle of the window associated with the thread
   HWND hWindow;

   // the state of a thread in the pool
   eThreadState tsState;

   int   iIndex0;
   HANDLE hThread;
   DWORD dwThreadId;
};

// maximum number of parallel running threads
#define  MAX_THREADS 4

// the threadpool
CThreadPool cpThread[MAX_THREADS];


// random result in range 0..1
#define Random1() ((double) rand() / (double)RAND_MAX)


// find a vacant thread in the pool
int FindFreeThread()
{
   // search for an unused thread
   for (int i = 0; i < MAX_THREADS; i++)
   {
      if (cpThread[i].tsState != Active)
         return (i);
   } // for (int i = 0; i < MAX_THREADS; i++)

   // no free thread found
   return (-1);
} // FindFreeThread


// find a thread by the window associated to the thread
int FindThreadByWindow(HWND hWindow)
{
   // search for the thread that created the window
   for (int i = 0; i < MAX_THREADS; i++)
   {
      if (cpThread[i].hWindow == hWindow)
         return (i);
   } // for (int i = 0; i < MAX_THREADS; i++)

   // no thread found for the window
   return (-1);
} // FindThreadByWindow


// Check if all threads are finished before program ends
BOOL AllThreadsFinished()
{
   // search for an thread that is not finished yet
   for (int iThread0 = 0; iThread0 < MAX_THREADS; iThread0++)
   {
      if ((cpThread[iThread0].tsState != Finished) && (cpThread[iThread0].tsState != Unused))
         return (FALSE);
   } // for (int iThread0 = 0; iThread0 < MAX_THREADS; iThread0++)

   // all threads are finished
   return (TRUE);
} // AllThreadsFinished


// WndProc for the about-dialog
INT_PTR CALLBACK AboutDlgProc (HWND hDlg, UINT iMessage, WPARAM wParam,
                                 LPARAM lParam)
{
   switch (iMessage)
   {
      case  WM_COMMAND:
         switch (LOWORD(wParam))
         {
            case    IDOK:
               EndDialog (hDlg, 0);
               break;
         }
         break;

      default:
         return (FALSE);
   } // switch (iMessage)

   return (TRUE);
} // AboutDlgProc


// show the about-dialog
void About (HWND hWndParent)
{
   DialogBox (hInstanceMain, MAKEINTRESOURCE(IDD_ABOUTBOX),
      (HWND)hWndParent, (DLGPROC)AboutDlgProc);
} // About


// place the window according to the index iIndex0 in the main-window
void PlaceWindow (HWND hWnd, int iIndex0)
{
   RECT rcMainArea;
   GetClientRect(hWndMain, &rcMainArea);

   int x, y;
   x = ((rcMainArea.right - rcMainArea.left) / 2) * ((iIndex0 & 0x02) ? 1 : 0);
   y = ((rcMainArea.bottom - rcMainArea.top) / 2) * ((iIndex0 & 0x01) ? 1 : 0);

   // set size and position ! THIS IS CRITICAL WITH WINDOWS 10 2004
   SetWindowPos (hWnd, NULL, x, y, (rcMainArea.right - rcMainArea.left) / 2, (rcMainArea.bottom - rcMainArea.top) / 2, SWP_NOZORDER | SWP_SHOWWINDOW);
} // PlaceWindow


//  Processes messages for the child window.
LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message)
   {
      case WM_CREATE:
         SetTimer(hWnd, 1, 50, NULL);
         break;


      case WM_COMMAND:
      {
         int wmId = LOWORD(wParam);

         // Parse the menu selections:
         switch (wmId)
         {
            case IDM_ABOUT:
               About(hWnd);
               break;

            case IDM_EXIT:
               DestroyWindow(hWnd);
               break;

            default:
               return DefWindowProc(hWnd, message, wParam, lParam);
         }
      }
      break;


      case WM_TIMER:
         // randomly remove the window
         if (Random1() > 0.98)
            DestroyWindow(hWnd);

         {
            int iThread0;
            iThread0 = FindThreadByWindow(hWnd);

            SYSTEMTIME stTime;
            TCHAR sTime[32];
            GetLocalTime(&stTime);
            wsprintf(sTime, TEXT("%02d:%02d:%02d - Window %d"), stTime.wHour, stTime.wMinute, stTime.wSecond, iThread0);

            HDC hdc = GetDC(hWnd);
            TextOut(hdc, 30, 30, sTime, lstrlen(sTime));
            ReleaseDC(hWnd, hdc);

            // set also in the title for convenience
            SetWindowText(hWnd, sTime);
         }

         // close all windows on exit
         if (fCloseAll)
            DestroyWindow(hWnd);

         break;


      case WM_PAINT:
      {
         PAINTSTRUCT ps;
         HDC hdc = BeginPaint(hWnd, &ps);
         EndPaint(hWnd, &ps);
      }
      break;


      case  WM_CLOSE:
         DestroyWindow(hWnd);
         break;

      default:
         return DefWindowProc(hWnd, message, wParam, lParam);
   }

   return 0;
} // WndProcChild



DWORD WINAPI ThreadChildWindow (void* pvThreadPool)
{
   // type conversion for easier access
   CThreadPool* pThreadPool = (CThreadPool*)pvThreadPool;

   // Thread should not be ended so far
   fEndChildWindowThread = FALSE;

   // initialize Random-Function
   srand((unsigned)time(NULL));

   // keep in the loop until the external end is set on program end
   while (!fEndChildWindowThread)
   {
      // No window associated? Then create a new one
      if (pThreadPool->hWindow == NULL)
      {
         // create the window - invisible with dummy position and size
         pThreadPool->hWindow = CreateWindow(WC_CHILDWINDOW, TEXT("Child Window"), WS_CHILD | WS_BORDER | WS_CAPTION | WS_SYSMENU, 1, 1, 100, 100, hWndMain, NULL, hInstanceMain, NULL);

         // the problem does NOT appear with WS_POPUP, but with WS_CHILD only
         // pThreadPool->hWindow = CreateWindow(WC_CHILDWINDOW, TEXT("Child Window"), WS_POPUP | WS_BORDER | WS_CAPTION | WS_SYSMENU, 1, 1, 100, 100, hWndMain, NULL, hInstanceMain, NULL);

         // correct size and position
         PlaceWindow(pThreadPool->hWindow, pThreadPool->iIndex0);
       } // if (pThreadPool->hWindow == NULL)


      MSG msg;

      // message loop for this thread and child
      if (PeekMessage(&msg, pThreadPool->hWindow, 0, 0, PM_REMOVE))
      {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      } // if (PeekMessage(&msg, pThreadPool->hWindow, 0, 0, PM_REMOVE))

      // if this is no valid window any more, then suspend the thread behind
      if (!IsWindow(pThreadPool->hWindow))
      {
         // mark the thread as suspended
         pThreadPool->tsState = Suspended;

         // no more window is associated with this thread
         pThreadPool->hWindow = NULL;

         // Suspend this thread
         SuspendThread(pThreadPool->hThread);
      } // if (!IsWindow(pThreadPool->hWindow))
   } // while (!fEndChildWindowThread)

   // mark the thread as finished (for the end of program)
   pThreadPool->tsState = Finished;

   // end the thread   
   ExitThread (TRUE);

   // give something back to avoid warnings
   return (0);
} // ThreadChildWindow



// create a new thread for a child window (the window is created within the thread)
void CreateThreadWindow()
{
   // search for a free thread in the pool
   int iThread0 = FindFreeThread();

   // No more free thread? Then exit.
   if (iThread0 == -1)
      return;

   // create a new thread (suspended)
   if (cpThread[iThread0].tsState == Unused)
      cpThread[iThread0].hThread = CreateThread (NULL, 0, ThreadChildWindow, (PVOID)&cpThread[iThread0], CREATE_SUSPENDED, &cpThread[iThread0].dwThreadId);

   // mark the thread as active
   cpThread[iThread0].tsState = Active;

   // resume the thread
   ResumeThread(cpThread[iThread0].hThread);
} // CreateThreadWindow


//  Processes messages for the main window.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message)
   {
      case  WM_CREATE:
      {
         // remember the main-window
         hWndMain = hWnd;

         // a timer to create a window in the remote thread
         SetTimer (hWnd, 1, 1000, NULL);
      }
      break;


      case WM_TIMER:
         // create a Thread that opens a second window as child
         CreateThreadWindow();

         break;


      case WM_COMMAND:
      {
         int wmId = LOWORD(wParam);
         // Parse the menu selections:
         switch (wmId)
         {
            case IDM_ABOUT:
               DialogBox(hInstanceMain, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, AboutDlgProc);
               break;

            case IDM_EXIT:
               DestroyWindow(hWnd);
               break;

            default:
               return DefWindowProc(hWnd, message, wParam, lParam);
         }
      }
      break;


      case WM_PAINT:
      {
         PAINTSTRUCT ps;
         HDC hdc = BeginPaint(hWnd, &ps);
         // end the paint block
         EndPaint(hWnd, &ps);
      }
      break;


      case WM_DESTROY:
         // close all child windows
         fCloseAll = TRUE;
         PostQuitMessage(0);
         break;

      default:
         return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
} // WndProc



//  Registers the window class.
ATOM MyRegisterClass(HINSTANCE hInstance)
{
   WNDCLASSEXW wcex;

   wcex.cbSize = sizeof(WNDCLASSEX);

   wcex.style = CS_HREDRAW | CS_VREDRAW;
   wcex.lpfnWndProc = WndProc;
   wcex.cbClsExtra = 0;
   wcex.cbWndExtra = 0;
   wcex.hInstance = hInstance;
   wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SAMPLETHREADWINDOW));
   wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
   wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
   wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_SAMPLETHREADWINDOW);
   wcex.lpszClassName = WC_MAINWINDOW;
   wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

   RegisterClassEx(&wcex);

   wcex.cbSize = sizeof(WNDCLASSEX);

   wcex.style = CS_HREDRAW | CS_VREDRAW; // | CS_OWNDC;
   wcex.lpfnWndProc = WndProcChild;
   wcex.lpszClassName = WC_CHILDWINDOW;

   return RegisterClassEx(&wcex);
} // MyRegisterClass


// main
int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE,
                     LPSTR /*lpszCmdLine*/, int nCmdShow)
{
   // Initialize global strings
   hInstanceMain = hInstance;
   MyRegisterClass(hInstance);


   // clean the threadpool
   ZeroMemory (cpThread, sizeof(cpThread));


   // set the index for the threads (just for the position of the window associated to the thread)
   for (int i = 0; i < MAX_THREADS; i++)
   {
      cpThread[i].iIndex0 = i;
   } // for (int i = 0; i < MAX_THREADS; i++)


   // create and show the main window
   HWND hWndMain = CreateWindow(WC_MAINWINDOW, MAIN_TITLE, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                                 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
   ShowWindow(hWndMain, nCmdShow);
   UpdateWindow(hWndMain);

   MSG msg;

   // Main message loop:
   while (GetMessage(&msg, hWndMain, 0, 0))
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);

      // leave the thread if the window is destroyed
      if (!IsWindow(hWndMain))
         break;
   } // while (GetMessage(&msg, nullptr, 0, 0))

   // also end the Child-Window-Threads
   fEndChildWindowThread = TRUE;

   Sleep(100);

   // resume all suspended threads so they can finish
   for (int iThread0 = 0; iThread0 < MAX_THREADS; iThread0++)
   {
      if (cpThread[iThread0].tsState == Suspended)
      {
         // mark the thread as active
         cpThread[iThread0].tsState = Active;

         // resume the thread
         ResumeThread(cpThread[iThread0].hThread);
      } // if (cpThread[iThread0].tsState == Suspended)
   } // for (iThread0)


   // wait for the end
   while (!AllThreadsFinished())
   {
      Sleep(10);
   } // while (!AllThreadsFinished())

   return (int)msg.wParam;
} // WinMain

SampleThreadWindow.rc

#include "resource.h"
#include "windows.h"

// Menu
IDC_SAMPLETHREADWINDOW MENU
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&xit",                IDM_EXIT
    END
    POPUP "&Help"
    BEGIN
        MENUITEM "&About ...",           IDM_ABOUT
    END
END


// Dialog
IDD_ABOUTBOX DIALOGEX 0, 0, 170, 62
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "About SampleThreadWindow"
FONT 8, "MS Shell Dlg"
BEGIN
    LTEXT           "SampleThreadWindow, Version 1.0",IDC_STATIC,42,14,114,8,SS_NOPREFIX
    LTEXT           "Copyright (C) 2020",IDC_STATIC,42,26,114,8
    DEFPUSHBUTTON   "OK",IDOK,113,41,50,14,WS_GROUP
END

Resource.h

#define IDS_APP_TITLE           103

#define IDR_MAINFRAME           128
#define IDD_SAMPLETHREADWINDOW_DIALOG   102
#define IDD_ABOUTBOX            103
#define IDM_ABOUT               104
#define IDM_EXIT                105

#define IDI_SAMPLETHREADWINDOW          120
#define IDI_SMALL                           121
#define IDC_SAMPLETHREADWINDOW          122

#define IDC_MYICON              2
#ifndef IDC_STATIC
    #define IDC_STATIC              -1
#endif


Solution 1:[1]

It looks like I found the reasons for the program stopping. So I want to share the things I found.

Thread Pool

The first problem was the usage of a threading pool. I created a thread that created a window. After the windows was not used any more, the window was closed and removed. The thread was suspended until it was reused to start another window. This does not work as Drake Wu wrote in the other answer. For me this looks like a bug.

Manipulating Other Windows

As the software started multithreading in 2002 it was no problem to manipulate windows that had been created in another thread (The worst manipulation is a call to SetWindowPos). This must not be done any longer. As Drake Wu wrote "when the parent window sends some message (like minimize, maximize), the parent window will wait for the response of the child window. In the previous version, there was no waiting." This waiting seems to last forever if the window was not created within the thread that sends the message.

No Exceptions

In other programming languages (like C#) an exception is raised if a window should be accessed that is not owned by the current thread. Using the Windows-API with C++ nothing happens... not true, an endless wait is happening. Unfortunately the wait can be at an innocent command like SetWindowPos that has nothing to do with the real problem. But the message loop of a window is stopping to work and SetWindowPos never returns as it is waiting for answers.

The changes MS made might have been necessary for security. But the missing exceptions and the problems with the thread pool made the search very hard. I hope the hints of Drake Wu and my conclusion can help others with the same problems.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1