C# WaitHandle.WaitAny和信号量类

C# WaitHandle.WaitAny和信号量类,c#,.net,multithreading,waithandle,C#,.net,Multithreading,Waithandle,编辑:我想为我提出这个问题而辩护,但这在当时是有意义的(见下面的编辑2) 对于.NET3.5项目,我需要检查两种类型的资源(R1和R2)的可用性。每种资源类型在任何时候都可以有(比如)10个实例 当任一类型的资源可用时,我的工作线程需要唤醒(线程数量可变)。在早期的一个实现中,只有一种资源类型,我使用信号量检查其可用性 现在我需要等待两个单独的信号量(S1和S2),它们跟踪资源的可用性 WaitHandle[] waitHandles = new WaitHandle[] { s1, s2 };

编辑:我想为我提出这个问题而辩护,但这在当时是有意义的(见下面的编辑2)

对于.NET3.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文档中:

如果多个对象成为 在呼叫过程中发出信号,返回 值是数组的数组索引 具有最小 所有信号的索引值 对象

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

处理这个问题的最好方法是什么?我是否应该从使用两个信号量切换到简单计数器,并在任何一个计数器发生更改时使用自动重置事件来发送信号?我是否错过了一些更优雅的方法

编辑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”)


您可能要查看的另一个资源:。虽然它不是“官方”源,但我认为没有理由认为它不可靠。

出于您的目的,在调用
WaitHandle.WaitAny()时
method结果无关紧要。重要的是一个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()
方法


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

在本例中,您无条件释放两个信号量。MSDN的另一句话:程序员有责任确保线程不会多次释放信号量。”。如果执行此
WaitAny
/
release
序列两次,
s2.release()
将引发异常:将给定计数添加到信号量将导致其超过其最大计数。是的,但这是一个示例,这就是为什么我将信号量实例化为max counter==2,因此我知道我可以释放它们一次,而不必担心异常,我在这里证明的是,即使两个信号量都已释放(counter>0)和WaitAny()返回0(从您的引号-最小索引中),只有第一个信号量被锁定,而另一个信号量保持不变。同样的修改似乎也支持您的说法,即只有一个信号量将被更改(我个人觉得在信号量上下文中锁定有点混乱),但我希望看到一些官方规范声明这是有保证的。Locked也不适合我,尽管我找不到更好的替代品(修改后的有一个缺点,它没有说明它是否被释放或“锁定”)。至于保证-MSDN声称,WaitAny()返回“满足等待的对象的数组索引”。",从理论上讲,这表明我们谈论的是一个对象,而且只有一个。在参考文献中,他们没有提到改变多个对象的状态。我认为Ravadre是对的。从我所理解的,试图传达的是这样一个事实,在
WaitAny
返回后,只有
信号量
,the返回的e索引已被获取。因此,即使另一个
信号量
同时被释放,它也不会被获取,因此不需要被释放。当然,我不确定这一点,我也没有可以指向您的文档。但是,它似乎应该是这样工作的。我添加了一个有价值的(虽然不是官方的)您可能要检查的源。读取,等待它不会获取信号量本身。