You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows应用因PeekMessage()运行缓慢的问题排查与解决咨询

问题描述

我开发了一款无标题栏边框的Windows应用,可通过键盘输入绘制字符0、1、2。用g++编译后,应用启动异常缓慢(图标加载需3-4秒,整体启动耗时6-7秒),还会导致整机卡顿,但任务管理器未检测到内存泄漏或高CPU占用。排查后发现内层while(PeekMessage())循环是问题根源,但移除该循环后,应用仅能显示窗口,无法实现核心功能。我用PeekMessage()是为了实现无消息时仍能在屏幕绘制内容,想知道问题原因及修复方案。

代码如下:

#include <windows.h>
#include <stdint.h>
#include <stdio.h>

#define glyph_width 5
#define glyph_height 5

int glyph[3][glyph_width * glyph_height] = {{0,1,1,0,0,
                                  1,0,1,1,0,
                              1,0,0,1,0,
                          1,1,0,1,0,
                          0,1,1,0,0},

                         {0,0,1,0,0,
                                  0,1,1,0,0,
                              0,0,1,0,0,
                          0,0,1,0,0,
                          0,1,1,1,0},

                         {0,1,1,0,0,
                                  1,0,0,1,0,
                              0,0,1,0,0,
                          0,1,0,0,0,
                          1,1,1,1,0}};

bool Running;
uint32_t* BitmapBuffer;
int BitmapWidth;
int BitmapHeight;

BITMAPINFO BitmapInfo;

uint32_t color = 0x00ffffff;
int box_x = 0;
int box_y = 0;

LRESULT CALLBACK WindowProcedure(HWND WindowHandle, uint32_t Message, WPARAM WParam, LPARAM LParam){
    LRESULT Result = 0;

    switch(Message){
        case WM_CREATE:{
            BitmapWidth = ((CREATESTRUCT*) LParam)->cx;
            BitmapHeight = ((CREATESTRUCT*) LParam)->cy;
            VirtualFree(BitmapBuffer, 0, MEM_RELEASE);
            BitmapBuffer = (uint32_t*) VirtualAlloc(0, BitmapWidth * BitmapHeight * 4, MEM_COMMIT, PAGE_READWRITE);
            
            BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFO);
            BitmapInfo.bmiHeader.biWidth = BitmapWidth;
            BitmapInfo.bmiHeader.biHeight = -BitmapHeight;
            BitmapInfo.bmiHeader.biPlanes = 1;
            BitmapInfo.bmiHeader.biBitCount = 32;
            BitmapInfo.bmiHeader.biCompression = BI_RGB;
        } break;
        
        case WM_KEYDOWN:
        case WM_KEYUP:
        case WM_SYSKEYDOWN:
        case WM_SYSKEYUP:{
            uint16_t VKCode = (uint16_t) WParam;
            bool WasKeyDown = (LParam & (1 << 30));
            bool IsKeyDown = !(LParam & (1 << 31)); 
        
            if(IsKeyDown){
                for(int y = 0; y < glyph_height; y++){
                    for(int x = 0; x < glyph_width; x++){
                        if(glyph[VKCode - 48][y * glyph_width + x] == 1)
                        BitmapBuffer[(y + box_y * glyph_height) * BitmapWidth + (x + box_x * glyph_width)] = color;
                    }
                }
                box_x += 1;
            }
        } break;

        case WM_PAINT:{
            SetDIBitsToDevice(GetDC(WindowHandle), 0, 0, BitmapWidth, BitmapHeight, 0, 0, 0, BitmapHeight, BitmapBuffer, &BitmapInfo, DIB_RGB_COLORS);
        } break;
        
        default:{
            Result = DefWindowProc(WindowHandle, Message, WParam, LParam);
        }
    }

    return Result;
}

int main(){

    WNDCLASS WindowClass = {};
    WindowClass.lpfnWndProc = WindowProcedure;
    WindowClass.lpszClassName = "WindowClassName";
    WindowClass.hInstance = (HINSTANCE) GetModuleHandle(0);
    RegisterClass(&WindowClass);

    AllocConsole();
    freopen("conin$", "r", stdin);
    freopen("conout$", "w", stdout);
    freopen("conout$", "w", stderr);

    HWND Window = CreateWindow(WindowClass.lpszClassName, "Editor", WS_POPUP, 0, 0, 800, 800, 0, 0, WindowClass.hInstance, 0);
    
    ShowWindow(Window, 1);

    Running = true;
    while(Running){
        MSG Message = {};
        while(PeekMessage(&Message, 0, 0, 0, PM_REMOVE)){
            TranslateMessage(&Message);
            DispatchMessage(&Message);
        }
        
    }
}

问题原因

你的主循环采用忙轮询方式:当没有消息时,PeekMessage会立即返回FALSE,外层while循环会无限重复调用PeekMessage,导致单个CPU核心被持续占满(任务管理器可能因整体CPU使用率未达标而不显示异常)。这种忙等待会抢占大量系统时间片,导致系统其他进程(比如资源管理器加载图标、窗口初始化服务)被延迟执行,从而出现启动缓慢、整机卡顿的现象。

修复方案

需要在无消息时让出CPU,避免忙轮询,同时保留无消息时的绘制能力。以下是两种常用方案:

方案1:添加短暂休眠(简单快速)

在处理完所有消息后,调用Sleep(1)让线程休眠1毫秒,给系统其他进程让出时间片。同时补充处理WM_QUIT消息,避免窗口关闭后进程残留:

Running = true;
while(Running){
    MSG Message = {};
    while(PeekMessage(&Message, 0, 0, 0, PM_REMOVE)){
        if(Message.message == WM_QUIT){
            Running = false;
            break;
        }
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    // 无消息时休眠1ms,避免忙轮询
    Sleep(1);
    // 此处可添加无消息时的绘制逻辑,比如触发窗口重绘
    // InvalidateRect(Window, NULL, FALSE);
}

方案2:使用MsgWaitForMultipleObjects(更高效)

对于需要持续绘制的场景,MsgWaitForMultipleObjects可以在等待消息的同时让出CPU,直到有消息或超时,适合做定时绘制:

Running = true;
while(Running){
    MSG Message = {};
    // 等待消息,超时时间1ms(可根据绘制帧率调整)
    DWORD waitResult = MsgWaitForMultipleObjects(0, NULL, FALSE, 1, QS_ALLINPUT);
    if(waitResult == WAIT_OBJECT_0){
        while(PeekMessage(&Message, 0, 0, 0, PM_REMOVE)){
            if(Message.message == WM_QUIT){
                Running = false;
                break;
            }
            TranslateMessage(&Message);
            DispatchMessage(&Message);
        }
    }
    // 无消息时执行绘制逻辑
    // InvalidateRect(Window, NULL, FALSE);
}

额外优化点

  1. 触发重绘:当前代码修改BitmapBuffer后未触发窗口更新,需在键盘消息处理中添加InvalidateRect(WindowHandle, NULL, FALSE),让系统通过WM_PAINT刷新画面。
  2. 按键范围限制:添加对VKCode的范围判断(仅处理0-2按键),避免数组越界。

修改后的键盘消息处理代码:

case WM_KEYDOWN:
case WM_KEYUP:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:{
    uint16_t VKCode = (uint16_t) WParam;
    bool WasKeyDown = (LParam & (1 << 30));
    bool IsKeyDown = !(LParam & (1 << 31)); 

    if(IsKeyDown && VKCode >= '0' && VKCode <= '2'){
        for(int y = 0; y < glyph_height; y++){
            for(int x = 0; x < glyph_width; x++){
                if(glyph[VKCode - 48][y * glyph_width + x] == 1)
                BitmapBuffer[(y + box_y * glyph_height) * BitmapWidth + (x + box_x * glyph_width)] = color;
            }
        }
        box_x += 1;
        // 触发窗口重绘
        InvalidateRect(WindowHandle, NULL, FALSE);
    }
} break;

内容的提问来源于stack exchange,提问作者Prateek Bana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 22:57:03