需要了解SemaphoreSlim的用法


90

这是我的代码,但我不知道SemaphoreSlim在做什么。

async Task WorkerMainAsync()
{
    SemaphoreSlim ss = new SemaphoreSlim(10);
    List<Task> trackedTasks = new List<Task>();
    while (DoMore())
    {
        await ss.WaitAsync();
        trackedTasks.Add(Task.Run(() =>
        {
            DoPollingThenWorkAsync();
            ss.Release();
        }));
    }
    await Task.WhenAll(trackedTasks);
}

void DoPollingThenWorkAsync()
{
    var msg = Poll();
    if (msg != null)
    {
        Thread.Sleep(2000); // process the long running CPU-bound job
    }
}

等待ss.WaitAsync();ss.Release();做什么?

我想如果我一次运行50个线程,然后编写类似的代码,SemaphoreSlim ss = new SemaphoreSlim(10);那么它将被迫一次运行10个活动线程。

当10个线程之一完成时,另一个线程将启动。如果我不对,请帮助我了解示例情况。

为什么await需要ss.WaitAsync();?怎么ss.WaitAsync();办?


3
要注意的一件事是,您确实应该包装“ DoPollingThenWorkAsync();” 在“尝试{DoPollingThenWorkAsync();}最后{ss.Release();}”中,否则异常将使该信号永久地饿死。
奥斯丁·萨尔加特

对于我们分别在任务内部/外部获取和释放信号量,我感到有些奇怪。在任务内部移动“ await ss.WaitAsync()”会有所不同吗?
Shane Lu

Answers:


73

我想如果我一次运行50个线程,则类似SemaphoreSlim ss = new SemaphoreSlim(10)的代码;将强制一次运行10个活动线程

那是正确的; 使用信号量可确保同时进行这项工作的工人不超过10名。

调用WaitAsync信号量将产生一个任务,当该线程已被“访问”该令牌时,该任务将完成。 await-ing任务使程序在“允许”执行该操作时继续执行。具有异步版本(而不是调用)Wait对于确保方法保持异步而不是同步非常重要,而且还应解决async由于回调而导致方法可以跨多个线程执行代码的事实,等等。与信号量的自然线程亲和力可能是一个问题。

旁注:DoPollingThenWorkAsync不应有Async后缀,因为它实际上不是异步的,而是同步的。只是叫它DoPollingThenWork。它将减少读者的困惑。


谢谢,但是请告诉我,当我们没有指定要运行的线程数(例如10)时,会发生什么情况。当10个线程之一完成时,该线程又跳到另一项工作上或回到池中?这不是很清楚...。所以请解释一下幕后发生的事情。

@Mou不清楚吗?代码将一直等待,直到当前正在运行的任务少于10个为止。如果有,它将添加另一个。任务完成后,表明任务已完成。而已。
Servy

指定不运行线程的好处是什么。如果线程过多可能会影响性能?如果是的话,那为什么会妨碍...如果我运行50个线程而不是10个线程,那么为什么性能会很重要...请您解释一下。感谢
Thomas

4
@Thomas如果并发线程太多,则线程花费的上下文切换时间要多于进行生产工作的时间。吞吐量随着线程的增加而下降,这是因为您花费越来越多的时间来管理线程而不是进行工作,至少在线程数大大超过了计算机核心数之后。
Servy 2013年

3
@Servy这是任务计划程序工作的一部分。任务!=线程。该Thread.Sleep原代码将摧毁任务调度。如果您不与核心异步,那么您也不是异步的。
Joseph Lennox

54

在拐角处的幼儿园,他们使用SemaphoreSlim控制可在体育室玩耍的孩子人数。

他们在房间外面的地板上画了5对脚印。

当孩子们到达时,他们将鞋子放在免费的脚印上,进入房间。

一旦玩完了,他们就会出来,收拾鞋子,为另一个孩子“释放”一个插槽。

如果一个孩子到了并且没有脚印了,他们会去别处玩或者只是待一会儿然后不时检查一下(即没有FIFO优先级)。

当老师在附近时,她会在走廊的另一侧“释放”额外的5个脚印行,以使另外5个孩子可以同时在房间里玩耍。

它还具有SemaphoreSlim的相同“陷阱”。

如果一个孩子完成游戏并离开房间而没有拿起鞋子(不会触发“释放”),则即使理论上有一个空的插槽,插槽也会被阻塞。不过,孩子通常会被告知。

有时,一个或两个偷偷摸摸的孩子将鞋子藏在别处,然后进入房间,即使所有的脚印都已被占用(即SemaphoreSlim也不“真正”控制房间里有多少个孩子)。

这通常不能很好地结束,因为教室的过度拥挤往往会因孩子哭泣和老师完全关闭教室而结束。


3
这些答案是我的最爱。
我的堆栈溢出

天哪,这真是太有趣了!
Zonus

7

尽管我接受这个问题确实与倒计时锁定方案有关,但我认为值得分享这些链接,我希望将SemaphoreSlim用作简单的异步锁定的人可以使用此链接。它允许您使用using语句,可以使编码更整齐,更安全。

http://www.tomdupont.net/2016/03/how-to-release-semaphore-with-using.html

我确实在它的Dispose中进行了交换_isDisposed=true_semaphore.Release()以防万一它被多次调用。

同样重要的是要注意SemaphoreSlim不是可重入锁,这意味着如果同一线程多次调用WaitAsync,则信号量所具有的计数每次都会减少。简而言之,SemaphoreSlim不支持线程。

关于代码质量问题,最好将Release放在最后的尝试中,以确保始终将其发布。


6
建议不要发布仅链接的答案,因为链接会随着时间的流逝而消失,从而使答案一文不值。如果可以,最好将关键点或关键代码块汇总到您的答案中。
约翰
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.