【发布时间】:2020-10-14 18:51:22
【问题描述】:
我编写了一个应用程序,其主窗口打开多个子窗口作为子窗口 (WS_CHILD) - 即状态行、带有按钮的栏和内容部分。到目前为止,主窗口是其他窗口的框架并在后台运行。它还组织消息等等。内容部分可以被其他窗口替换为其他内容。每个窗口都有自己的线程,它使用某种编程语言来填充窗口的内容。为了加快程序的变化,我使用了线程池。因此,如果一个窗口关闭,它会将线程释放到挂起模式,并且下一个打开的窗口可以再次使用该线程。
到目前为止,一切都很好。这种结构可以正常运行 10 多年,是多个项目的基础。
现在随着 Windows 10 20H1 的最新升级,该程序停止工作。看起来 windows 中的消息队列正在工作一段时间,然后在几秒钟后挂断在 DispatchMessage 中。尽管底层线程只是在等待工作,但一些窗口在 WndProc 中不再接收任何消息。 达到此目的的最快方法是不使用 WS_CHILD 而是使用 WS_POPUP 打开一个窗口。然后窗口出现,但它没有收到绘画消息并且永远不会被填充。只是框架而已,在这之后,程序就不再行动了。
我试图找到从一个线程发送到另一个线程的 PostMessage 和 SendMessage。曾经有一个观点,但现在已经被淘汰了(过去也没出什么问题)。
另一个想法是删除线程池并为每个新窗口使用一个新线程。但这并没有使任何事情变得更好。唯一有帮助的是保持打开所有创建的窗口。由于程序经常运行一整天甚至几天,这也不是解决方案。我无法在后台保留数百个窗口。
任何想法可能是什么问题?或者有人对windows 20H1升级有类似经历吗?
不幸的是,这个基础上的程序正在世界各地的许多计算机上运行,并且一些客户已经升级了 Windows。快速的解决方案是回滚升级,但这当然不是永恒的解决方案。
编辑:这里我有一个问题示例:
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
【问题讨论】:
-
大约十年前,您已被警告:Is it legal to have a cross-process parent/child or owner/owned window relationship? 没有理由跨线程传播您的 UI。
-
线程是属于同一个进程还是不同进程都没有关系。当您开始跨线程传播 UI 时,效果是相同的。你喊出来的理由不是理由。有无数种方法可以将工作卸载到工作线程并让它们与 UI 通信。 C++20 协程使这比以往任何时候都容易。整个 WinUI 实现是这样工作的。任何 Microsoft Store 应用和任何 UWP 应用都具有单线程 UI。
-
这不是你应该问的问题。将“代码尚未失败”误认为“代码正确”。它不是,也从来没有。除非您愿意接受,否则您无法解决问题。如果您坚持认为 Microsoft 破坏了您完美运行的代码,您将不得不通过可用的支持渠道之一联系供应商。如果这不是一个选项,并且您仍然希望 Stack Overflow 帮助您,请提供minimal reproducible example。
-
我不是 Windows API 方面的专家,因此无法回答您的实际问题,但我可以回答:“将 UI 分布在某些线程上的原因是保持窗口正常工作而另一个人可能会从事更长的任务。”处理长任务的常用方法是启动与 any 窗口无关的线程。然后您可以使用简单的非 GUI 原语(例如
std::mutex、std::condition_variable)将工作发送到这些线程,并在工作线程中使用PostMessage将数据(结果或只是进度更新)发送到 GUI 线程。 -
更新:好像在20H1,当父窗口发送一些消息(如最小化,最大化)时,父窗口会等待子窗口的响应。在以前的版本中,没有等待。设计发生了变化,你需要组织你的窗口消息逻辑来避免上述情况。
标签: c++ multithreading winapi