WaitHandle.WaitAny和Semaphore类

时间:2009-08-19 13:51:39

标签: c# .net multithreading waithandle

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

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

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

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

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文档:

  

如果有多个对象变为   在通话期间发出信号,返回   value是数组的索引   最小的信号对象   所有信号的索引值   对象。

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

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

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

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

2 个答案:

答案 0 :(得分:5)

如果我理解你的问题,我认为你的解决方案是完全正常的,而你只是在解释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。虽然它不是“官方”来源,但我认为没有理由认为它不可靠。

答案 1 :(得分:1)

出于您的目的,在调用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()方法。

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