WaitForMultipleObjects与WSAWaitForMultipleEvents

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WaitForMultipleObjects与WSAWaitForMultipleEvents相关的知识,希望对你有一定的参考价值。

WSAWaitForMultipleEvents可以完全替代WaitForMultipleObjects?
WaitForMultipleObjects不可完全替代WSAWaitForMultipleEvents?

我的判断是否正确

参考技术A WaitForSingleObject 和 WaitForMultipleObjects:
1.WaitForSingleObject
等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止。这些等待函数中最常用的是WaitForSingleObject:
DWORD WaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds);
当线程调用该函数时,第一个参数hObject标识一个能够支持被通知/未通 知的内核对象。第二个参数dwMilliseconds.允许该线程指明,为了等待该对象变为已通知状态,它将等待多长时间。调用下面这个函数将告诉系 统,调用函数准备等待到hProcess句柄标识的进程终止运行为止:
WaitForSingleObject(hProcess, INFINITE);
第二个参数告诉系统,调用线程愿意永远等待下去(无限时间量),直到该进程终止运行。
通常情况下, INFINITE是作为第二个参数传递给WaitForSingleObject的,不过也可以传递任何一个值(以毫秒计算)。顺便说一下, INFINITE已经定义为0xFFFFFFFF(或-1)。当然,传递INFINITE有些危险。如果对象永远不变为已通知状态,那么调用线程永远不会 被唤醒,它将永远处于死锁状态,
不过,它不会浪费宝贵的CPU时间。
下面是如何用一个超时值而不是INFINITE来调用WaitForSingleObject的例子:
DWORD dw = WaitForSingleObject(hProcess, 5000);
switch(dw)

case WAIT_OBJECT_0:
// The process terminated.
break;
case WAIT_TIMEOUT:
// The process did not terminate within 5000 milliseconds.
break;
case WAIT_FAILED:
// Bad call to function (invalid handle?)
break;

上面这个代码告诉系统,在特定的进程终止运行之前,或者在5 0 0 0 m s时间结束之前,调用线程不应该变为可调度状态。因此,如果进程终止运行,那么这个
函数调用将在不到5000ms的时间内返回,如果进程尚未终止运行,那么它在大约5000ms时间内返回。注意,不能为dwMilliseconds传递0。如果传递了0,WaitForSingleObject函数将总是立即返回。
2.WaitForMultipleObjects
WaitForSingleObject的返回值能够指明调用线程为什么再次变为可调度状态。如果线程等待的对象变为已通知状态,那么返回值是 WAIT_OBJECT_0。如果设置的超时已经到期,则返回值是WAIT_TIMEOUT。如果将一个错误的值(如一个无效句柄)传递给 WaitForSingleObject,那么返回值将是WAIT_FAILED(若要了解详细信息,可调用GetLastError)。
下面这个函数WaitForMultipleObjects与WaitForSingleObject函数很相似,区别在于它允许调用线程同时查看若干个内核对象的已通知状态:
DWORD WaitForMultipleObjects(DWORD dwCount,
CONST HANDLE* phObjects,
BOOL fWaitAll,
DWORD dwMilliseconds);
dwCount参数用于指明想要让函数查看的内核对象的数量。这个值必须在1与MAXIMUM_WAIT_OBJECTS(在Windows头文件中定义为64)之间。phObjects参数是指向内核对象句柄的数组的指针。
可以以两种不同的方式来使用WaitForMultipleObjects函数。
一种方式是让线程进入等待状态,直到指定内核对象中的任何一个变为已通知状态。
另一种方式是让线程进入等待状态,直到所有指定的内核对象都变为已通知状态。fWaitAll参数告诉该函数,你想要让它使用何种方式。如果为该参数传递TRUE,那么在所有对象变为已通知状态之前,该函数将不允许调用线程运行。
dwMilliseconds参数的作用与它在WaitForSingleObject中的作用完全相同。如果在等待的时候规定的时间到了,那么该函数无论如何都会返回。同样,通常为该参数传递INFINITE,但是在编写代码时应该小心,以避免出现死锁情况。
WaitForMultipleObjects函数的返回值告诉调用线程,为 什么它会被重新调度。可能的返回值是WAIT_FAILED和WAIT_TIMEOUT,这两个值的作用是很清楚的。如果fWaitAll参数传递 TRUE,同时所有对象均变为已通知状态,那么返回值是WAIT_OBJECT_0。如果为fWaitAll传递FALSE,那么一旦任何一个对象变为已 通知状态,该函数便返回。在这种情况下,你可能想要知道哪个对象变为已通知状态。返回值是WAIT_OBJECT_0 与(WAIT_OBJECT_0 + dwCount-1)之间的一个值。换句话说,如果返回值不是WAIT_TIMEOUT,也不是WAIT_FAILED,那么应该从返回值中减去 WAIT_OBJECT_0。产生的数字是作为第二个参数传递给WaitForMultipleObjects的句柄数组中的索引。该索引说明哪个对象变 为已通知状态。
下面是说明这一情况的一些示例代码:
HANDLE h[3];
h[0] = hProcess1;
h[1] = hProcess2;
h[2] = hProcess3;
DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);
switch(dw)

case WAIT_FAILED:
// Bad call to function (invalid handle?)
break;
case WAIT_TIMEOUT:
// None of the objects became signaled within 5000 milliseconds.
break;
case WAIT_OBJECT_0 + 0:
// The process identified by h[0] (hProcess1) terminated.
break;
case WAIT_OBJECT_0 + 1:
// The process identified by h[1] (hProcess2) terminated.
break;
case WAIT_OBJECT_0 + 2:
// The process identified by h[2] (hProcess3) terminated.
break;

如果为fWaitAll参数传递 FALSE,WaitForMultipleObjects就从索引0开始向上对句柄数组进行扫描,同时已通知的第一个对象终止等待状态。这可能产生一些 你不希望有的结果。例如,通过将3个进程句柄传递给该函数,你的线程就会等待3个子进程终止运行。如果数组中索引为0的进程终止运 行,WaitForMultipleObjects就会返回。这时该线程就可以做它需要的任何事情,然后循环反复,等待另一个进程终止运行。如果该线程传 递相同的3个句柄,该函数立即再次返回WAIT_OBJECT_0。除非删除已经收到通知的句柄,否则代码就无法正确地运行。

等待 WaitForMultipleObjects

【中文标题】等待 WaitForMultipleObjects【英文标题】:Waiting on WaitForMultipleObjects 【发布时间】:2010-01-10 17:42:11 【问题描述】:

我正在尝试为我的 FileWatcher 类编写单元测试。

FileWatcher 派生自 Thread 类并使用 WaitForMultipleObjects 在其线程过程中等待两个句柄:

    FindFirstChangeNotification返回的句柄 允许我取消上述等待的事件句柄。

所以基本上FileWatcher 正在等待先发生的事情:文件更改或我告诉它停止观看。

现在,当尝试编写测试此类的代码时,我需要等待它开始等待。

Peusdo 代码:

FileWatcher.Wait(INFINITE)
ChangeFile()
// Verify that FileWatcher works (with some other event - unimportant...)

问题是存在竞争条件。我需要首先确保 FileWatcher 已经开始等待(即它的线程现在在 WaitForMultipleObjects 上被阻塞),然后才能触发第 2 行中的文件更改。我不想使用 Sleeps,因为它看起来很笨拙,并且在调试时肯定会给我带来问题。

我熟悉SignalObjectAndWait,但它并不能真正解决我的问题,因为我需要它来“SignalObjectAndWaitOnMultipleObjects”...

有什么想法吗?


编辑

为了澄清一点,这里是FileWatcher 类的简化版本:

// Inherit from this class, override OnChange, and call Start() to turn on monitoring. 
class FileChangeWatcher : public Utils::Thread

public:
    // File must exist before constructing this instance
    FileChangeWatcher(const std::string& filename);
virtual int Run();
    virtual void OnChange() = 0;
;

它继承自Thread并实现线程函数,看起来像这样(非常简化):

_changeEvent = ::FindFirstChangeNotificationW(wfn.c_str(), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE);

HANDLE events[2] =  _changeEvent, m_hStopEvent ;

DWORD hWaitDone = WAIT_OBJECT_0;
while (hWaitDone == WAIT_OBJECT_0)

    hWaitDone = ::WaitForMultipleObjects(2, events, FALSE, INFINITE);

    if (hWaitDone == WAIT_OBJECT_0)
        OnChange(); 
    else
        return Thread::THREAD_ABORTED;
 
return THREAD_FINISHED;

请注意,线程函数等待两个句柄,一个 - 更改通知,另一个 - “停止线程”事件(继承自 Thread)。

现在测试这个类的代码如下所示:

class TestFileWatcher : public FileChangeWatcher

public:
    bool Changed;
    Event evtDone;
    TestFileWatcher(const std::string& fname) : FileChangeWatcher(fname)  Changed = false; 
    virtual void OnChange()
    
        Changed = true;
        evtDone.Set();
    
;

And 从 CPPUnit 测试中调用:

std::string tempFile = TempFilePath();
StringToFile("Hello, file", tempFile);
TestFileWatcher tfw(tempFile);
tfw.Start();
::Sleep(100); // Ugly, but we have to wait for monitor to kick in in worker thread
StringToFile("Modify me", tempFile);
tfw.evtDone.Wait(INFINITE);
CPPUNIT_ASSERT(tfw.Changed);

这个想法是摆脱中间的睡眠。

【问题讨论】:

【参考方案1】:

没有比赛,您不必等待FileWatcher 输入WaitForMultipleObjects。如果您在调用函数之前执行更改,它将立即返回。

编辑:我现在可以看到比赛了。为什么不移动以下行

_changeEvent = ::FindFirstChangeNotificationW(/*...*/);

从线程函数到FileChangeWatcher的构造函数?这样,您可以确定在调用 StringToFile 函数时,该文件已被监视。

【讨论】:

您的意思是我可以在调用 Wait 之前更改文件,但 创建更改通知句柄之后?但这个类的情况并非如此,它不会将更改句柄的创建作为事件公开。或者也许我误解了你.. 但是没有其他方法可以做到这一点。实现这一点的标准方法是打开要等待的对象(在您的情况下创建文件观察程序并启动它),向另一个对象发出信号,然后等待第一个对象。这就是它的完成方式,你需要改变你的类设计以适应这种情况。 你不是在FileWatcher的构造函数中调用FindFirstChangeNotification吗?从那一刻起,该目录应该受到关注。也许您可以发布更多代码,以便我们知道到底发生了什么。 我没有从构造函数调用 FFCN。但是现在我正在尝试这样做,但我仍然遇到了比赛条件。当我让它在没有断点的情况下运行时,文件更改对于观察者来说很快就会发生并且它会永远等待。当我在更改文件之前在单元测试代码中放置一些断点,然后让它在点击它之后继续运行时,一切正常(延迟就足够了)。 划掉最后一条评论。其他一些错误(在上面的代码中不可见)导致它卡住了。将其移至构造函数确实可以解决问题。【参考方案2】:

您应该在观察者的构造函数中调用FindFirstChangeNotification() 并存储它返回的句柄以在您的线程函数中使用。这意味着您将从构建的那一刻起就捕捉到变化事件。

一旦您的线程启动,它只需在两个句柄上调用 wait 即可。如果在线程启动之前发生了更改,那么FindFirstChangeNotification() 返回的句柄将已经发出信号并且更改将被处理。如果您希望线程监控许多更改,那么它应该在处理每个通知后循环并调用FindNextChangeNotification()

【讨论】:

【参考方案3】:

你可以使用互斥锁吗?在一个线程可以访问它想要的资源之前,它必须锁定 Mutex 并为其他需要该资源的线程解锁它。

【讨论】:

你指的是什么资源?在这种情况下,您如何建议我使用互斥锁?【参考方案4】:

调用 CreateEvent() 来创建一个无信号事件。当观察者线程进入其主循环(或其他)时,SetEvent()。同时,在 FileWatcher 中首先 WaitForSingleObject() 事件,然后一旦返回,WFMO 就像你之前做的那样。

【讨论】:

但这会产生另一种竞争条件。

以上是关于WaitForMultipleObjects与WSAWaitForMultipleEvents的主要内容,如果未能解决你的问题,请参考以下文章

WaitHandle.WaitAny 匹配 WaitForMultipleObjects 功能

在 WaitForMultipleObjects 窗口中等待可变数量的事件

WaitForMultipleObjects的使用问题

WaitForMultipleObjects 到底啥意思,啥作用

MFC线程操作 WaitForMultipleObjects返回值

waitformultipleobjects 具有未知数量的句柄