C# WaitHandle.WaitAny和信号量类
编辑:我想为我提出这个问题而辩护,但这在当时是有意义的(见下面的编辑2) 对于.NET3.5项目,我需要检查两种类型的资源(R1和R2)的可用性。每种资源类型在任何时候都可以有(比如)10个实例 当任一类型的资源可用时,我的工作线程需要唤醒(线程数量可变)。在早期的一个实现中,只有一种资源类型,我使用信号量检查其可用性 现在我需要等待两个单独的信号量(S1和S2),它们跟踪资源的可用性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 };
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索引已被获取。因此,即使另一个信号量
同时被释放,它也不会被获取,因此不需要被释放。当然,我不确定这一点,我也没有可以指向您的文档。但是,它似乎应该是这样工作的。我添加了一个有价值的(虽然不是官方的)您可能要检查的源。读取,等待它不会获取信号量本身。