为什么C#中没有像Java一样可以获取多个许可的信号量?

5
为什么C#中没有获取多个信号量许可的方法?我想这样使用我的信号量:
semaphore.Acquire(2);

6
你的问题是如何做,还是你的问题是“为什么它不像Java那样工作”?如果你想得到第一个问题的答案,你需要重新表达你的问题。你将得不到关于第二个问题的答案。 - Thorsten Dittmar
7
实际上,我可以猜测第二个问题的答案:“因为C#开发团队经过深思熟虑,找到了一些很好的理由不像Java那样做。”或者说类似但不太友好的说法是:“因为这可能是一个不太经过深思熟虑的Java特性,每个人都只是出于习惯使用它”。 - CompuChip
@CompuChip:实际上可能都不是。C#开发团队甚至没有实现库函数。而这个功能可能没有被足够频繁地使用,以至于让.Net团队实现它需要做的工作不值得。 - Gabe
很好的观点,@Gabe。+1 为你的回答解释了这一点。 - CompuChip
4个回答

6
良好的API设计是一门精良的艺术,只由少数人真正掌握。其中一个重要方面是强调共性。.NET Semaphore类具有这种特性,您可以使用WaitOne()获取信号量。就像您在所有同步类上所做的那样。
而真正成为一门精良的艺术,则需要知道什么是需要去掉的。定制的Aquire(n)方法将高居该清单之首。这是一种非常麻烦的方法,Windows上的底层本地实现也不支持它。这会产生导致死锁的严重风险,几乎无法进行调试,代码将挂起在一个循环中的不可见方法内,深埋在CLR中。这增加了等待时需要终止代码时候的语义,这些语义对于需要被中止的代码非常重要。您可以自己循环,这是一个简单的解决方法并且在调试死锁时易于查看,因此您可以至少检查计数器。

4
.Net的Semaphore对象是Win32 Semaphore对象的包装器。由于Win32信号量没有获取多个资源的机制,因此在.Net Framework中提供它并不容易。
当然,微软可能会实现自己的抽象来覆盖Win32信号量,但这样就不能在WaitAll调用中使用。很可能微软从未考虑过实现这个功能。

他们本来可以将它添加到SemaphoreSlim中,但似乎他们主要保留了Semaphore类的API。 - Julien Roncaglia

1
如果您只想检查许可证是否可用,并且不需要阻止,则可以以简单且安全(没有死锁)的方式实现。
public static bool Acquire (this Semaphore semaphore, int permits) {
    for (int i = 0; i < permits; i++) {
        /*
         * If millisecondsTimeout is zero, the method does not block.
         * It tests the state of the wait handle and returns immediately.
         */
        if (!semaphore.WaitOne (0)) {
            semaphore.Release (i);
            return false;
        }
    }
    return true;
}

这可以很容易地修改以包括超时。但是,要无限期阻塞会更困难(它需要信号,否则会导致忙碌的外部循环)。

1

您可以编写自己的扩展方法到Semaphore类中:

namespace Extentions
{
    public static class SemaphoreExtentions
    {

        public static void Acquire(this Semaphore semaphore, int permits)
        {
           for (int i = 0; i < permits; ++i)
               semaphore.WaitOne();
        }
    }
}

使用方法:

using Extentions;

//...

public void SomeMethod()
{
    var sem = new Semaphore(10, 10);
    sem.Acquire(6);
}

更新 为避免死锁:
public static void Acquire(this Semaphore semaphore, int permits)
{
    lock (semaphore)
    {
        for (int i = 0; i < permits; ++i)
            semaphore.WaitOne();        
    }
}

当然,您不应该在其他地方锁定此信号量,因为这也可能导致死锁。另一个选择是将方法的签名更改为Acquire(this Semaphore semaphore, int permits, object sync)并锁定sync对象。

3
这将导致死锁。在你的例子中,如果有两个客户端同时尝试获取6,会发生什么?假设它们都获得了5 -- 那么它们将互相等待彼此放弃最后一个1。 - Gabe
我认为Semaphore的所有者对象必须担心其成员的线程安全性。 - Deffiss
@Gabe 你是关于我的答案还是关于Mixer的评论? - Deffiss
我的评论是关于答案的。Mixer的评论建议一种避免死锁的方法。 - Gabe
2
使用 lock (semaphore) 不是一个好主意。需要一个锁对象,只能由试图获取给定信号量的调用者锁定,例如 readonly object acquireLock = new object(); ... lock (acquireLock) ... 原因:Acquire 可以持有这个锁很长时间,因为它积累了足够的许可。假设其他地方的代码希望为某些其他目的 lock (semaphore) - 它将被卡在等待此代码上。不幸的是,传递一个 sync 对象可能会导致相反的问题:两个不同的调用者,对于相同的信号量具有不同的同步对象,可能会死锁。... - ToolmakerSteve
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接