WaitHandle.WaitAny和Semaphore类

编辑:我甚至想问这个问题暂时疯狂,但当时有意义(见下面的编辑2)。

对于.NET 3.5项目,我有两种类型的资源( R1R2 ),我需要检查它的可用性。 每种资源类型可以随时(例如)有10个实例。

当其中一种资源可用时,我的工作线程需要唤醒(存在可变数量的线程)。 在早期的实现中,只有一种资源类型,我使用信号量来检查可用性。

现在我需要等待两个单独的信号量( S1S2 )来跟踪资源的可用性。

WaitHandle[] waitHandles = new WaitHandle[] { s1, s2 }; int signalledHandle = WaitHandle.WaitAny(waitHandles); switch (signalledHandle) { case 0: // Do stuff s1.Release(); case 1: // Do stuff s2.Release(); } 

然而,这有一个问题。 从WaitAny上的MSDN文档:

如果在调用期间发出多个对象的信号,则返回值是信号对象的数组索引,其中所有信号对象的索引值最小。

这表明在调用WaitAny之后我可能会将我的信号量计数减少1。 因为signalledHandle将指示s1已发出信号,我将开始使用资源R1 ,并在完成后释放它。 但是,由于我不知道S2是否已发出信号,因此此资源的可用性计数现在可能已关闭。 如果发生这种情况10次,我的信号量将永久“空”,资源R2将不再使用。

处理这个问题的最佳方法是什么? 我是否应该从使用两个信号量切换到简单计数器,并在任何一个计数器更改时发出AutoResetEvent信号? 我错过了一些更优雅的方法吗?

编辑1:
根据Ravadre的说法,在WaitAny之后,只有一个信号量会被改变。 略微修改他的例子似乎证实了这一点,但有没有人可以指出我指出这个的一些官方文档?

编辑2:
我在回家的路上想着这个。 只有这样我才意识到WaitAny必须是有用的。 这个问题不仅限于信号量,而是几乎任何类型的同步对象,使WaitAny几乎无用。

如果我理解你的问题,我认为你的解决方案是完全正常的,而你只是在解释msdn引用。 当调用WaitHandle.WaitAny()您将获得最低的索引,但是您将仅锁定一个waitHandle(在本例中为信号量),请检查以下示例代码:

 Semaphore s1 = new Semaphore(1, 2); Semaphore s2 = new Semaphore(1, 2); WaitHandle[] handles = new WaitHandle[] { s1, s2 }; int x = WaitHandle.WaitAny(handles); int prevS1 = s1.Release(); int prevS2 = s2.Release(); 

在这种情况下, prevS1将等于0,因为信号量s1 “等待”,因此它的计数器已减少为0,而prevS2将等于1,因为它的状态在实例化后没有改变( Release()方法在释放之前返回计数器,因此返回1意味着“它是1,现在它是2”。

您可能想要查看的另一个资源: http : //www.albahari.com/threading/part2.aspx#_Wait_Handles 。 虽然它不是“官方”来源,但我认为没有理由认为它不可靠。

出于您的目的,在调用WaitHandle.WaitAny()方法时,结果无关紧要。 重要的是一个WaitHandle被发出信号,所以你需要再次尝试获取锁定/同步。

 void Main() { var semaphoreOne = new SemaphoreSlim(0, 1); var semaphoreTwo = new SemaphoreSlim(0, 1); ReleaseSemaphoreAfterWhile(semaphoreOne); bool firstAccepted; bool secondAccepted = false; while ((firstAccepted = semaphoreOne.Wait(0)) == false && (secondAccepted = semaphoreTwo.Wait(0)) == false) { var waitHandles = new [] { semaphoreOne.AvailableWaitHandle, semaphoreTwo.AvailableWaitHandle }; WaitHandle.WaitAny(waitHandles); Console.WriteLine("SemaphoreOne Before Lock = " + semaphoreOne.CurrentCount); Console.WriteLine("SemaphoreTwo Before Lock = " + semaphoreTwo.CurrentCount); } if (firstAccepted) { Console.WriteLine("semaphore 1 was locked"); } else if (secondAccepted) { Console.WriteLine("semaphore 2 was locked"); } else { throw new InvalidOperationException("no semaphores were signaled"); } } Random rd = new Random(); public void ReleaseSemaphoreAfterWhile(SemaphoreSlim semaphore) { var sleepWork =(int)rd.Next(100, 1000); ThreadPool.QueueUserWorkItem(t => { Thread.Sleep(10000 + sleepWork); semaphore.Release(); }); } 

有其他实现的空间可以使用相同的构思/逻辑,但是以这种方式使用while循环可以保证只获取一个信号量,如果没有空间,它会锁定线程,直到任何WaitHandle被发出信号 – 考虑SemaphoreSlim实例.Release()方法。

不幸的是(正如评论中所指出的那样)他们对网络中的线程同步存在一些误解,但上面的代码应该可以帮助您解决问题。