'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 |
