第322页 GDI与窗口管理
koo
读过 Win32多线程程序设计
- 章节名:GDI与窗口管理
- 页码:第322页 2014-04-01 10:42:58
线程的消息队列
让我们先花一些时间来比较 Win16 和 Win32 两者的消息队列。在 Win16 中,所有窗口共享同一个消息队列。如果某个程序停止处理消息队列中 的数据(也就是消息),那么所有的窗口就都会停止回应。这在 Windows 3.x 中是一个严重的问题,也是系统之所以会被锁死而不再做出反应的最大原因。 在 Win32 中,每一个线程有它自己专属的消息队列。这并不意味着每一 个窗口有它自己的消息队列,因为一个线程可以产生许多窗口。如果一个线程 停止回应,或是它忙于一段耗时的计算工作,那么由它所产生的窗口统统都会 停止回应,但系统中的其他窗口还是继续正常运作。 引自 GDI与窗口管理 以下是一个非常基本的规则,用来管理 Win32 中的线程、消息、窗口的 互动:
所有传送给某一窗口之消息,将由产生该窗口之线程负责处理。 你对窗口所做的任何一件事情基本上都会被该窗口的窗口函数(那个CALLBACK函数,窗口回调)处理,并因此被产生该窗口的线程处理。 引自 GDI与窗口管理 消息如何周游列国
当你进入一个窗口函数时,如果观察 Windows NT 中的调用堆栈(call stack),通常总是可以追踪回到 WinMain(),那是你的程序的起始点(Windows 95 则因为 16-bit thunking(译注)的缘故,故事不是这样发展)。你会在调用 堆栈(call stack)中看到某些函数位于 USER32.DLL 或 KERNEL32.DLL 之 中。这些函数用来将消息派送(dispatch)到适当的窗口函数去,并调用该窗 口函数。 所谓 thunking,可以说是一种转换,把 16 位函数调用(包括其中的参数以 及参数所代表或隐含的地址等等)转换为 32 位函数调用。或是反向转换。 16 位转为 32 位称为 thunk up,32 位转为 16 位称为 thunk down,而其间 的转换机制称为 thunking layer。 引自 GDI与窗口管理 Windows 会自动计算出哪一个线程应该收到消息,以及线程应该如何被 告知说有这么一个消息进来。一共有四种可能,列于下表。
请注意,当你“send”一个消息给另一线程所掌握的窗口时,会发生什么 事情?系统必须做一次 context switch,切换到另一线程去,调用该窗口函数, 然后再做一次 context switch 切换回来。与一般的函数调用比起来,其间的额 外负担毋宁说是太大了些。 这个巨大的额外负担正是为什么“每个 MDI 窗口不应该有一个线程” 的头号原因。其间的牵连倒不是立刻就能浮现出来,毕竟一个 MDI 程序背后 的观念是要让每一个窗口能够独立工作,所以似乎不会喜欢让各个窗口间常有 沟通。问题出在主窗口! 时髦的 Windows 程序往往挂上一大堆琳琅满目的“勋章”:工具栏(toolbars)啦、状态栏(status bars)啦、调色板(palettes)啦。它们统统必须被处理、被更新状态、被致能(enabled)或除能(disabled)。这些动作通常由目前作用中的子窗口负责,因为这些“勋章”的状态系由作用中的 子窗口决定。在 MFC 中,这个行为已经被 OnUpdate() 完全承担下来。如果这些“勋章”存活于不同的线程中,那么更新其状态可能需要数百次 context switches。这样的结果将对效率带来严重的冲击。 引自 GDI与窗口管理 睡眠之中还能工作的线程
有一件事情很重要,必须了解:当你“send”一个消息出去时,你的线程 将暂时处于睡眠状态。这使得 SendMessage() 就像一个典型的函数调用操作。 虽然你的线程正在等待 SendMessage() 的返回,但它还是可以处理外界对 其拥有之窗口的任何 SendMessage() 调用操作,甚至即使线程不处于主消息循 环(其中有 GetMessage() 和 DispatchMessage() 操作)之中。如果 Windows 不 这样设计,那么该线程所拥有的其他任何窗口就会停止反应,并且无法回答来 自外界的任何 SendMessage() 操作。 引自 GDI与窗口管理 虽然 waiting thread 可以处理来自 SendMessage() 的消息,但它不处理其 他种类(像是位于消息队列中)的消息。如果 destination thread 开启一个对话 框(译注:会将其父窗口除能的那种,也就是“modal dialog”),或是进入 一个消息循环中,以至于没有返回至 waiting thread 的 SendMessage() 调用 处,那么就可能引起一些问题。为了解决这问题,当 destination thread 调用以 下任何函数,waiting thread 必须自动醒来: DialogBox() DialogBoxIndirect() DialogBoxIndirectParam() DialogBoxParam() GetMessage() MessageBox() PeekMessage() 我们可以测试一个线程(译注:也就是前述的 waiting thread)是否正陷于 “SendMessage() 未返回”的进退维谷情况中,而且可以明白地让调用端(译 注:也就是前述的 waiting thread)醒来。这可能是有用的——如果你需要开始 一个长时间的计算工作,而发动计算的那个人(线程)不需要知道其结果的话。 如果某个线程(译注:也就是前述的 destination thread)正在处理由其他 线程“sent”过来的消息,所以你在该线程中调用 IsSendMessage(),会获得 TRUE。IsSendMessage() 不需要指定参数。 为了让调用端线程(译注:也就是前述的 waiting thread)能够继续工作, 我们可以调用 ReplyMessage()。 引自 GDI与窗口管理 消息在线程间流动的陷阱
当 Windows 必须在线程之间“send”消息时,不论是否这些线程位于相 同进程之中,总是有这种可能:destination thread 被锁死,以至于 waiting thread 永远醒不过来。 这个问题在你跨越进程“send”消息时特别突出,因为你没有办法保证传 送对象(目标线程)的行为。Win32 提供了两个函数协助解决这个问题。 第一个函数是 SendMessageTimeout()。它允许你指定一个时间,时间终了 后不管对方怎么样,SendMessageTimeout() 一定会返回。如果对方“挂”了, 它也会自动返回。 第二个函数是 SendMessageCallback()。这个函数会立刻返回,但其参数 之一,一个函数地址,应该被以 SendMessage() 的方式调用起来。 引自 GDI与窗口管理 以Worker 线程完成多线程版MDI 程序
欲在一个 MDI 程序中有效率地使用多个线程,我的建议是,以一个线程处理所有的用户输入,以及所有用户界面的管理,然后使用一个以上的线程来负责诸如重绘、打印等工作。或许你还需要一些线程,用来处理你的磁盘I/O或网络 I/O。不管你如何切割你的工作,很重要的一点是,你的主线程(负责主框架窗口)总是应该能够有所回应,不会陷入长时间计算的泥淖中。 引自 GDI与窗口管理 线程之间的通讯
线程常常需要将数据传递给另一个线程。Worker 线程可能需要告诉别人说它的工作完成了,GUI 线程则可能需要交给 worker 线程一件新工作。 PostThreadMessage() 的操作就像 PostMessage() 一样,但是它的参数之一不是窗口 handle,而是线程 ID。当然,收受端线程必须有消息队列,不过至少这个队列是由操作系统管理,你不必操心。 PostThreadMessage() 把消息“post”给一个线程,而非一个窗口。如果收受端线程尝试获取目标窗口的 handle,它会得到 NULL。所以,收受端线程的消息循环应该有特殊的处理方式,以处理窗口函数之外的消息。以消息当做通讯方式,比起标准技术如使用全局变量等,有很大的好处。 如果目标线程很忙碌,则负责“post”的那个线程不会因此停滞下来。 如果对象是同一进程中的线程,你可以自定义消息,如WM_APP + 0x100等等,并配置一块结构,放置你想传递的数据,然后把结构指针当做lParam。收受端应该在处理完消息后负责释放内存。 如果你使用 PostThreadMessage() 在不同进程的线程之间传递消息,你必须使用 WM_COPYDATA 消息,这样一来数据才能够从一个地址空间中被映射到另一个地址空间。 引自 GDI与窗口管理
3人阅读
koo对本书的所有笔记 · · · · · ·
-
第190页 Overlapped I/O,在你深厚变戏法
许多应用程序,例如终端机模拟程序,都需要在同一时间处理对一个以上的文件的读写操作。利用 ...
-
第257页 使用C run-time library
如果你使用 MFC 来开发程序,注意,不要在一个MFC 程序中使用_beginthreadex() 或 CreateThre...
-
第322页 GDI与窗口管理
-
第305页 MFC中的线程
如果要在 MFC 程序中产生一个线程,而该线程将调用 MFC 函数或使用 MFC 的任何数据, 那么你...
-
第365页 进程之间的通讯
虽然我们已经讨论过,使用多重线程比使用多重进程的好处,但还是可 能因为某些理由使你决定使...
> 查看全部8篇
说明 · · · · · ·
表示其中内容是对原文的摘抄