SemaphoreSlimの使用法を理解する必要があります


94

これが私が持っているコードですが、何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個のスレッドの1つが完了すると、別のスレッドが開始されます。私が正しくない場合は、サンプルの状況を理解するのを手伝ってください。

なぜawait一緒に必要なのss.WaitAsync();ですか?何をしss.WaitAsync();ますか?


3
注意すべきことの1つは、その「DoPollingThenWorkAsync();」を実際にラップする必要があるということです。「try {DoPollingThenWorkAsync();} final {ss.Release();}」で、そうでない場合、例外はそのセマフォを永久に枯渇させます。
オースティンサルガット2018年

タスクの外側/内側でそれぞれセマフォを取得および解放するのは少し奇妙に感じます。タスク内で「awaitss.WaitAsync()」を移動しても違いはありますか?
シェーンルー

回答:


76

一度に50スレッドを実行すると、SemaphoreSlim ss = new SemaphoreSlim(10);のようなコードになると思います。一度に10個のアクティブなスレッドを実行するように強制します

それは正しいです; セマフォを使用すると、この作業を同時に行う作業者が10人を超えることはありません。

WaitAsyncセマフォを呼び出すと、そのスレッドがそのトークンへの「アクセス」を許可されたときに完了するタスクが生成されます。 await-そのタスクを実行すると、プログラムは「許可」されている場合でも実行を継続できます。を呼び出すのWaitではなく非同期バージョンを使用することは、メソッドが同期ではなく非同期のままであることを保証するためasync、およびコールバックのためにメソッドが複数のスレッドにわたってコードを実行できるという事実に対処するために重要です。セマフォとの自然なスレッドの親和性が問題になる可能性があります。

補足:実際には非同期ではなく、同期であるためDoPollingThenWorkAsyncAsync接尾辞を付ける必要はありません。ただそれを呼んでくださいDoPollingThenWork。それは読者の混乱を減らすでしょう。


感謝しますが、実行するスレッドの数を10と指定するとどうなるか教えてください。10個のスレッドのいずれかが終了すると、そのスレッドは別のジョブを終了するか、プールに戻りますか?これはあまり明確ではありません....舞台裏で何が起こっているのか説明してください。
Mou 2013年

@Mou何がはっきりしていないのですか?コードは、現在実行中のタスクが10未満になるまで待機します。ある場合、それは別のものを追加します。タスクが終了すると、タスクが終了したことを示します。それでおしまい。
Servy 2013年

実行するスレッドを指定しないことの利点は何ですか。スレッドが多すぎるとパフォーマンスが低下する可能性がある場合は?はいの場合、なぜ妨げになるのか... 10スレッドではなく50スレッドを実行する場合、パフォーマンスが重要になるのはなぜですか...説明してください。おかげで
トーマス

4
@Thomas同時スレッドが多すぎる場合、スレッドは生産的な作業に費やすよりもコンテキストの切り替えに多くの時間を費やします。少なくとも、スレッド数がマシンのコア数をはるかに超えると、作業を行う代わりにスレッドの管理に費やす時間が増えるにつれて、スレッドが増えるにつれてスループットが低下します。
Servy 2013年

3
@Servyこれはタスクスケジューラの仕事の一部です。タスク!=スレッド。Thread.Sleep元のコードでは、タスクスケジューラを荒廃でしょう。コアに非同期でない場合は、非同期ではありません。
ジョセフレノックス

67

角を曲がった幼稚園では、SemaphoreSlimを使用して、PEルームで遊ぶことができる子供たちの数を制御します。

彼らは部屋の外の床に5組の足跡を描いた。

子供たちが到着すると、彼らは靴を無料の足跡の上に置き、部屋に入ります。

演奏が終わったら、出てきて靴を集め、別の子供のためにスロットを「解放」します。

子供が到着し、足跡が残っていない場合、子供は他の場所で遊んだり、しばらく滞在して時々チェックします(つまり、FIFOの優先順位はありません)。

先生が近くにいるとき、彼女は廊下の反対側にある5つの足跡の余分な列を「解放」して、さらに5人の子供が同時に部屋で遊ぶことができるようにします。

また、SemaphoreSlimと同じ「落とし穴」があります...

子供が遊びを終えて靴を集めずに部屋を出た場合(「リリース」はトリガーされません)、理論的には空のスロットがありますが、スロットはブロックされたままになります。しかし、子供は通常、言われます。

すべての足跡がすでに取られている場合でも、1人か2人の卑劣な子供が靴を他の場所に隠して部屋に入ることがあります(つまり、SemaphoreSlimは部屋にいる子供たちの数を「実際に」制御しません)。

部屋の過密状態は子供たちが泣き、教師が部屋を完全に閉じることで終わる傾向があるため、これは通常うまく終了しません。


9
このような答えが私のお気に入りです。
私のスタックは

7

この質問は本当にカウントダウンロックのシナリオに関連していると思いますが、SemaphoreSlimを単純な非同期ロックとして使用したい人のために見つけたこのリンクを共有する価値があると思いました。それはあなたがコーディングをよりきちんとそしてより安全にすることができるusingステートメントを使うことを可能にします。

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

どういうわけか何度も呼び出された場合に備えて、Disposeでスワップ_isDisposed=true_semaphore.Release()アラウンドを行いました。

また、SemaphoreSlimは再入可能ロックではないことに注意することが重要です。つまり、同じスレッドがWaitAsyncを複数回呼び出すと、セマフォのカウントが毎回減少します。つまり、SemaphoreSlimはスレッドを認識しません。

質問のコード品質に関しては、リリースを試行の最後に入れて、最後にリリースされるようにすることをお勧めします。


6
リンクは時間の経過とともに消滅する傾向があり、回答が無価値になるため、リンクのみの回答を投稿することはお勧めできません。可能であれば、キーポイントまたはキーコードブロックを回答に要約することをお勧めします。
ジョン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.