所讨论的语言是 Nim,使用 wNim 库,但实际上问题是关于 win32 api,因为 wNim 是 Win32 gui 的一个非常轻量级的包装器。
我有一个单线程程序。当用户单击按钮时,它会启动一个长时间运行的计算。在每个第 n 个周期,计算偶尔会用 更新窗口InvalidateRect
,然后是UpdateWindow
。WM_PAINT
处理程序会立即在屏幕上绘制一些内容。有时我会引入一个sleep
几毫秒的 ,认为这会让系统WM_xxx
消息有时间赶上。
这通常可以正常工作。我可以在客户端区域以图形方式查看计算进度。
但是,当我尝试在更新期间移动窗口时(无论有没有sleep
),它不仅不响应移动(如果sleep(...)
省略,这是可以理解的),而且图形onPaint
完全停止,窗口变成程序死机时 Windows 呈现的幽灵白色。通常,命令行中的调试输出会继续。通常(但并非总是),命令行输出会停止,然后出现 Windows 对话框,告诉我程序崩溃了。我随时都可以在命令行中按 ctrl-c 并恢复正常。有人能解释一下是什么特定的事件序列和处理不当的消息导致一切都惨遭失败吗?
下面是伪代码。有没有一种适当的方法可以让长时间运行的前台计算更新客户端区域,同时响应其他用户事件,而无需多线程?或者多线程是这里最好的选择?我怀疑我的sleep(...)
调用是将一些周期返回到消息传递机制的地方。
伪代码:
proc onButton():
while(stuffNotDone):
doExpensiveStuff()
InvalidateRect(false) # <-- Win32 call
UpdateWindow() # <-- Win32 call
Sleep(10) # <-- Win32 call (optional)
proc onPaint(event):
# Responds to WM_PAINT
var dc = PaintDC(...) # <-- leads to Win32 BeginPaint
dc.blit(...) # <-- leads to Win32 call
SendMessage(hwnd, WM_APP+3,0,0) # <-- Win32 call to clear data elsewhere
如果您阅读了 SendMessage 的文档,您会发现,SendMessage 直到事件处理完毕才会返回。在某些情况下,如果处理消息会导致同一窗口中触发其他事件,则会导致递归,如果这会导致无效或非常长的处理程序调用链。要允许延迟处理,请使用 PostMessage。
UpdateWindow 绕过队列。它本质上直接使用绘制事件调用窗口过程。与调用此处理程序的窗口过程相同。处理程序中的休眠发生在绘制完成后,它会停止事件处理。
Windows GUI 中根本不会进行前台处理。它是在一个或多个线程中为每个可用窗口处理事件的同步循环(相比之下,大多数平台和多个 GUI 框架只允许在主线程中完成此操作)。延迟异步事件是使用计时器创建的。
要在不暂停窗口过程和事件循环的情况下处理数据,您必须有一个工作线程,这是自 1990 年代以来众所周知的范例。IIRC Nim 促进了异步处理。并且更容易创建线程,我想您必须在那里检查一下。
您的长时间运行计算阻止了调用线程处理新的 UI 消息。最好的选择是将计算移至工作线程,而根本不阻止主 UI 线程。但是,如果这不是您的选择,那么您只需要定期抽取 UI 消息队列,例如: