首页 > 解决方案 > 单独的线程以防止进度条在 C++ 中“冻结”

问题描述

我建立了一个最简单的进度条,它显示了我的 png 帧生成的进度。这是过程

BOOL myProc(HWND hwndParent, HINSTANCE hInst) {
    RECT rcClient;
    int cyVScroll;
    HWND hwndPB;
    DWORD cb;

    GetClientRect(hwndParent, &rcClient);
    cyVScroll = GetSystemMetrics(SM_CYVSCROLL);

    hwndPB = CreateWindowEx(0, PROGRESS_CLASS, (LPTSTR) NULL, 
                            WS_CHILD | WS_VISIBLE, rcClient.left, 
                            rcClient.bottom - cyVScroll, 
                            rcClient.right, cyVScroll, 
                            hwndParent, (HMENU) 0, hInst, NULL);
    int N = lastFrame;
    SendMessage(hwndPB, PBM_SETRANGE, 0, MAKELPARAM(0, N));
    SendMessage(hwndPB, PBM_SETSTEP, (WPARAM)1, 0);


    
    for (int i = 0; i < N + 1; i++) {
        int frame = create_pngInt(i);
        SendMessage(hwndPB, PBM_STEPIT, 0, 0);
        }

    DestroyWindow(hwndPB);
    DestroyWindow(hwndParent);
    return TRUE;

}

我希望我的窗口只承载这个进度条。当循环myProc完成后,主机窗口应该被销毁。

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE PrevInst, LPSTR args, int ncmdshow) {
    WNDCLASSW wc = { 0 };
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hInstance = hInst;
    wc.lpszClassName = L"GenPng";
    wc.lpfnWndProc = WindowProcedure;
    wc.hbrBackground = CreateSolidBrush(0x00171c00);
    wc.hIcon = hIcon;

    if (!RegisterClassW(&wc))
        return -1;
    
    hMainWindow = CreateWindowW(L"GenPng", L"Generating pngs", WS_VISIBLE | WS_OVERLAPPEDWINDOW,
        200,
        100, 300, 58, NULL, NULL, NULL, NULL);
    
    std::thread q(myProc, hMainWindow, hInst);
    MSG msg = { 0 };
    
    while (GetMessage(&msg, NULL, NULL, NULL))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}

但是,窗口hMainWindow不会被破坏(!)并且进程myProc不会自行终止(在 myProc 中的循环完成之后)。似乎我不明白一些重要的事情。

我检查了。如果我不创建单独的线程而只是myProc在 WinMain 中运行,则托管进度条的窗口在 myProc返回后会被破坏。而这正是我想要的。但是进度条会在几秒钟后冻结(。这就是为什么我需要一个单独的线程。

根据您的评论,我修改了如下代码:

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE PrevInst, LPSTR args, int ncmdshow) {
    RECT rect;
    WNDCLASSW wc = { 0 };
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hInstance = hInst;
    wc.lpszClassName = L"GenPng";
    wc.lpfnWndProc = WindowProcedure;
    wc.hbrBackground = CreateSolidBrush(0x00171c00);
    wc.hIcon = hIcon;

    if (!RegisterClassW(&wc))
        return -1;

    hMainWindow = CreateWindowW(L"GenPng", L"Generating pngs", WS_VISIBLE | WS_OVERLAPPEDWINDOW,
        200,
        100, 300, 58, NULL, NULL, NULL, NULL);
    SetWindowPos(hMainWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

    
    RECT rcClient;
    int cyVScroll;
    HWND hwndPB;
    DWORD cb;

    GetClientRect(hMainWindow, &rcClient);
    cyVScroll = GetSystemMetrics(SM_CYVSCROLL);

    hwndPB = CreateWindowEx(0, PROGRESS_CLASS, (LPTSTR)NULL,
        WS_CHILD | WS_VISIBLE, rcClient.left,
        rcClient.bottom - cyVScroll,
        rcClient.right, cyVScroll,
        hMainWindow, (HMENU)0, hInst, NULL);
    int N = lastFrame;
    SendMessage(hwndPB, PBM_SETRANGE, 0, MAKELPARAM(0, N));
    SendMessage(hwndPB, PBM_SETSTEP, (WPARAM)1, 0);
    
    std::thread q(myProc, hMainWindow, hInst, hwndPB);

    MSG msg = { 0 };
    while (GetMessage(&msg, hMainWindow, NULL, NULL))
    {
        if (msg.message == WM_QUIT) {
            DestroyWindow(hMainWindow);
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

但是,我知道我没有正确处理 WM_QUIT 事件,整个事情并没有被破坏。请您再详细说明一下,如果其余代码都可以吗?

标签: c++multithreadingwinapi

解决方案


调用的线程CreateWindowEx必须是执行GetMessage循环的线程。

它不会被破坏,因为没有任何东西为它处理事件和消息,包括WM_DESTROY消息。

将您的窗口创建保留在您的主线程中(并且仅限于该线程!),并仅使用PostMessage/SendMessage发送更新事件,最后将销毁消息发送到主线程拥有的窗口。

最终处理WM_DESTROY到你的主窗口,PostQuitMessage你就完成了。

在全:

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE PrevInst, LPSTR args, int ncmdshow) {
    RECT rect;
    WNDCLASSW wc = { 0 };
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hInstance = hInst;
    wc.lpszClassName = L"GenPng";
    wc.lpfnWndProc = WindowProcedure;
    wc.hbrBackground = CreateSolidBrush(0x00171c00);
    wc.hIcon = hIcon;

    if (!RegisterClassW(&wc))
        return -1;

    hMainWindow = CreateWindowW(
        L"GenPng", L"Generating pngs", WS_VISIBLE | WS_OVERLAPPEDWINDOW,
        200,
        100, 300, 58, NULL, NULL, NULL, NULL
    );
    SetWindowPos(hMainWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);


    RECT rcClient;
    int cyVScroll;
    HWND hwndPB;
    DWORD cb;

    GetClientRect(hMainWindow, &rcClient);
    cyVScroll = GetSystemMetrics(SM_CYVSCROLL);

    hwndPB = CreateWindowEx(
        0, PROGRESS_CLASS, (LPTSTR)NULL,
        WS_CHILD | WS_VISIBLE, rcClient.left,
        rcClient.bottom - cyVScroll,
        rcClient.right, cyVScroll,
        hMainWindow, (HMENU)0, hInst, NULL
    );
    int N = lastFrame;
    SendMessage(hwndPB, PBM_SETRANGE, 0, MAKELPARAM(0, N));
    SendMessage(hwndPB, PBM_SETSTEP, (WPARAM)1, 0);

    std::thread q([hMainWindow, hInst, hwndPB, N]() {
        for (int i = 0; i < N + 1; i++) {
            SendMessage(hwndPB, PBM_STEPIT, 0, 0);
        }
        DestroyWindow(hMainWindow);
    });

    MSG msg = { 0 };
    while (GetMessage(&msg, NULL, NULL, NULL))
    {
        if (msg.message == WM_DESTROY) {
            PostQuitMessage(0); 
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    q.join();
    
    // Exit code from PostQuitMessage() is still in msg.param
    return msg.param;
}

推荐阅读