7

我在关闭使用 WaitForSingleObject() 的应用程序时遇到问题,该应用程序具有无限超时。

完整的画面是这样的。我正在执行以下操作以允许我的应用程序处理设备唤醒事件:

通过以下方式注册活动:

CeRunAppAtEvent("\\\\.\\Notifications\\NamedEvents\\WakeupEvent",
    NOTIFICATION_EVENT_WAKEUP);

启动一个新线程等待:

Thread waitForWakeThread = new Thread(new ThreadStart(WaitForWakeup));
waitForWakeThread.Start();

然后在目标方法中执行以下操作:

private void WaitForWakeup()
{
    IntPtr handle = CreateEvent(IntPtr.Zero, 0, 0, "WakeupEvent");
    while (true)
    {
        WaitForSingleObject(handle, INFINITE);
        MessageBox.Show("Wakey wakey");
    }
}

这一切正常,直到我尝试关闭应用程序时,可以预见的是,WaitForSingleObject 继续等待并且不允许应用程序正确关闭。我们一次只允许我们的应用程序的一个实例运行,我们会在启动时检查这一点。它似乎会继续运行,直到设备软复位。

有没有办法杀死 WaitForSingleObject 正在等待的句柄,强制它返回?

非常感谢。

4

4 回答 4

19

请改用 WaitForMultipleObject,并传递 2 个句柄。现有的一个,一个用于称为“退出”之类的事件。在应用程序关闭期间,退出事件上的 SetEvent 和 WaitForMultipleObject 将返回,您可以让它优雅地退出线程。

您需要打开 WaitForMultipleObject 的返回值,以根据触发的句柄之一执行适当的行为。

您还可以将线程设置为后台线程。这将防止它在主线程终止时阻止您的应用程序关闭。

看:

http://msdn.microsoft.com/en-us/library/system.threading.thread.isbackground.aspx

于 2011-05-17T15:23:56.260 回答
2

这就是我会做的...

  1. 使用 EventWaitHandle 类而不是直接调用 CreateEvent。除了 CeRunAppAtEvent 之外,应该没有任何需要使用 Windows API(并且 API 调用使代码变得丑陋......)。先让这个工作。
  2. 在创建线程之前,创建一个最初未标记的 ManualResetEvent 变量。称之为“终止事件”。
  3. 将 WaitForSingleObject API 调用替换为 WaitHandle.WaitAny(WaitHandle[]) 并传递一个包含“TerminateEvent”的数组和包装 CeRunAppAtEvent 通知的 EventWaitHandle 类。
  4. 您的循环可以使用 WaitAny 的返回值来确定要做什么。返回值是解除线程阻塞的等待句柄的数组索引,可以判断是否继续循环。
  5. 要干净地结束线程,您可以在“TerminateEvent”上调用“Set”,然后“加入”线程以等待它终止。
于 2011-05-17T15:34:02.363 回答
1

“这一切都很好,直到我尝试关闭应用程序时,可以预见的是,WaitForSingleObject 继续等待并且不允许应用程序正确关闭。”

任何应用程序都可以关闭,无论其线程在做什么。如果您从应用程序中的任何线程调用 ExitProcess(0),应用程序将关闭,无论是否有线程在某些 API/sychro 上等待 INFINITE、睡眠、在另一个处理器上运行等等。操作系统会将所有未运行的线程的状态更改为“不再运行”,并使用其处理器间驱动程序硬中断任何其他实际运行您的线程代码的处理器。一旦所有线程停止,操作系统就会释​​放句柄、段等,您的应用程序将不再存在。

当开发人员尝试“干净地”关闭被卡住的线程时会出现问题 - 就像您的应用程序关闭时一样。所以..

在 OnClose/OnCloseQuery 处理程序、FormDestroy 或析构函数中是否有 TThread.WaitFor 或类似的?如果你有并且没有重要的理由来确保线程被终止,只需将其注释掉!

这允许主窗体关闭,因此您的代码最终将到达自您单击红十字按钮以来它一直试图到达的 ExitProcess()

当然,您可以自己调用 ExitProcess(),但这可能会使您的资源在其他进程中泄漏——例如,数据库连接。

'如果我不停止线程,则关闭时出现 216/217 错误'。这经常发生,因为开发人员遵循了呃……'不幸'的 Delphi 线程示例,并通过在辅助线程字段和主线程字段之间直接交换数据来与线程通信(例如 TThread.synchronize)。这简直糟透了,并且一心想引起问题,即使在应用程序运行中,当表单已被破坏并且线程正在尝试写入或线程已被破坏并且主线程表单时,更不用说关闭了尝试调用它的方法。通过排队/PostMessaging 对象与线程进行异步通信要安全得多,例如。在线程/表单中创建并在表单/线程中释放的对象,或者通过(线程安全的)在初始化部分中创建的对象池。

'我的表单句柄无效,因为它已关闭,但我的线程试图向它发布消息'。如果 PostMessage 例外,请退出您的线程。更好的方法与上述方法类似 - 仅将消息发布到比所有表单都长的窗口。在初始化部分中创建一个带有仅处理所有线程用于发布的一个 const 消息号的普通 WndProc 的初始化部分。您可以使用 wParam 传递 TwinControl实例线程试图与之通信的,(通常是表单变量),而 lParam 传递正在通信的对象。当它从线程获取消息时,WndProc 在传递的 TwinControl 上调用“Peform”,并且 TwinControl 将在消息处理程序中获取 comms 对象。例如,一个简单的全局布尔值“AppClosing”可以停止在关闭期间释放自身的 TwinControl 上的 WndProc 调用 Peform()。这种方法还避免了当操作系统使用不同的句柄重新创建表单窗口时出现的问题 - 不使用 Delphi 表单句柄并且 Windows 不会重新创建/更改在初始化中创建的简单表单的句柄。

几十年来我一直遵循这些方法并且没有遇到任何关闭问题,即使应用程序具有数十个线程在队列中抛掷对象也是如此。

Rgds,马丁

于 2011-05-18T09:07:12.807 回答
0

当然,解决这个问题的最佳方法是使用WaitForMultipleObjects, 或任何其他能够等待多个条件的合适函数(例如WaitForMultipleObjects,MsgWaitForMultipleObjects等)。

但是,如果您无法控制使用哪个函数 - 有一些棘手的方法可以解决这个问题。您可以hack通过在内存中更改任何模块的导入表来从系统 DLL 中导入函数。由于WaitForMultipleObjects是从 kernel32.dll 导出的 - 没关系。使用此技术,您可以将函数调用者重定向到您的手中,在那里您将能够使用WaitForMultipleObjects.

于 2011-05-17T15:25:22.577 回答