{"id":56019,"date":"2012-02-12T08:16:08","date_gmt":"2012-02-12T08:16:08","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/pfxteam\/2012\/02\/12\/building-async-coordination-primitives-part-5-asyncsemaphore\/"},"modified":"2012-02-12T08:16:08","modified_gmt":"2012-02-12T08:16:08","slug":"building-async-coordination-primitives-part-5-asyncsemaphore","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/building-async-coordination-primitives-part-5-asyncsemaphore\/","title":{"rendered":"Building Async Coordination Primitives, Part 5: AsyncSemaphore"},"content":{"rendered":"<p>In my last few posts, I covered building an <a href=\"https:\/\/blogs.msdn.com\/b\/pfxteam\/archive\/2012\/02\/11\/10266920.aspx\">AsyncManualResetEvent<\/a>, an <a href=\"https:\/\/blogs.msdn.com\/b\/pfxteam\/archive\/2012\/02\/11\/10266923.aspx\">AsyncAutoResetEvent<\/a>, an <a href=\"https:\/\/blogs.msdn.com\/b\/pfxteam\/archive\/2012\/02\/11\/10266930.aspx\">AsyncCountdownEvent<\/a>, and an <a href=\"https:\/\/blogs.msdn.com\/b\/pfxteam\/archive\/2012\/02\/11\/10266932.aspx\">AsyncBarrier<\/a>.&nbsp; In this post, I&rsquo;ll cover building an AsyncSemaphore class.<\/p>\n<p>Semaphores have a wide range of applicability.&nbsp; They&rsquo;re great for throttling, for protected access to a limited set of resources, and more.&nbsp; There are two public semaphore types in .NET: Semaphore (which wraps the Win32 equivalent) and SemaphoreSlim (which provides a lightweight counterpart built around Monitors).&nbsp; Here we&rsquo;ll build a simple async version, with the following shape:<\/p>\n<blockquote>\n<p><font size=\"2\" face=\"Consolas\"><font color=\"#0000ff\">public class<\/font> <font color=\"#4bacc6\">AsyncSemaphore<\/font>         <br>{         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">public<\/font> AsyncSemaphore(<font color=\"#0000ff\">int<\/font> initialCount);         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">public<\/font> <font color=\"#4bacc6\">Task<\/font> WaitAsync();         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">public void<\/font> Release();         <br>}<\/font><\/p>\n<\/blockquote>\n<p>The member variables we&rsquo;ll need look almost exactly the same as what was needed for the <a href=\"https:\/\/blogs.msdn.com\/b\/pfxteam\/archive\/2012\/02\/11\/10266923.aspx\">AsyncAutoResetEvent<\/a>, and for primarily the same reasons.&nbsp; We need to be able to wake individual waiters, so we maintain a queue of TaskCompletionSource&lt;bool&gt; instances, one per waiter.&nbsp; We need to keep track of the semaphore&rsquo;s current count, so that we know how many waits can complete immediately before we need to start blocking.&nbsp; And as we&rsquo;ll see, there&rsquo;s the potential for a fast path, so we maintain an already completed task that we can use repeatedly when the opportunity arises.<\/p>\n<blockquote>\n<p><font size=\"2\" face=\"Consolas\"><font color=\"#0000ff\">private readonly static<\/font> <font color=\"#4bacc6\">Task<\/font> s_completed = <font color=\"#4bacc6\">Task<\/font>.FromResult(<font color=\"#0000ff\">true<\/font>);         <br><font color=\"#0000ff\">private readonly<\/font> <font color=\"#4bacc6\">Queue<\/font>&lt;<font color=\"#4bacc6\">TaskCompletionSource<\/font>&lt;<font color=\"#0000ff\">bool<\/font>&gt;&gt; m_waiters = <font color=\"#0000ff\">new<\/font> <font color=\"#4bacc6\">Queue<\/font>&lt;<font color=\"#4bacc6\">TaskCompletionSource<\/font>&lt;<font color=\"#0000ff\">bool<\/font>&gt;&gt;();         <br><font color=\"#0000ff\">private<\/font> <font color=\"#0000ff\">int<\/font> m_currentCount;<\/font>       <br><\/p>\n<\/blockquote>\n<p>The constructor will just initial the count based on the caller&rsquo;s request:<\/p>\n<blockquote>\n<p><font size=\"2\" face=\"Consolas\"><font color=\"#0000ff\">public<\/font> AsyncSemaphore(<font color=\"#0000ff\">int<\/font> initialCount)         <br>{         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">if<\/font> (initialCount &lt; 0) <font color=\"#0000ff\">throw new<\/font> <font color=\"#4bacc6\">ArgumentOutOfRangeException<\/font>(<font color=\"#c0504d\">&#8220;initialCount&#8221;<\/font>);         <br>&nbsp;&nbsp;&nbsp; m_currentCount = initialCount;         <br>}<\/font>       <br><\/p>\n<\/blockquote>\n<p>Our WaitAsync method will also be reminiscent of the WaitAsync we wrote for <a href=\"https:\/\/blogs.msdn.com\/b\/pfxteam\/archive\/2012\/02\/11\/10266923.aspx\">AsyncAutoResetEvent<\/a>.&nbsp; We take a lock so as to ensure all of the work we do happens atomically and in a synchronized manner with the Release method we&rsquo;ll write shortly.&nbsp; Then, there are two possible paths.&nbsp; If the current count is above zero, there&rsquo;s still room left in the semaphore, so this wait operation can complete immediately and synchronously; in that case, we decrement the count and we return the cached completed task (this means that waiting on our semaphore in an uncontended manner requires no allocations).&nbsp; If, however, the current count is 0, then we create a new TaskCompletionSource&lt;bool&gt; for this waiter, add it to the list, and return its Task to the caller.<\/p>\n<blockquote>\n<p><font size=\"2\" face=\"Consolas\"><font color=\"#0000ff\">public<\/font> <font color=\"#4bacc6\">Task<\/font> WaitAsync()         <br>{         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">lock<\/font> (m_waiters)         <br>&nbsp;&nbsp;&nbsp; {         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">if<\/font> (m_currentCount &gt; 0)         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211;m_currentCount;         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">return<\/font> s_completed;         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">else<\/font>         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">var<\/font> waiter = <font color=\"#0000ff\">new<\/font> <font color=\"#4bacc6\">TaskCompletionSource<\/font>&lt;<font color=\"#0000ff\">bool<\/font>&gt;();         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_waiters.Enqueue(waiter);         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">return<\/font> waiter.Task;         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }         <br>&nbsp;&nbsp;&nbsp; }         <br>}<\/font><\/p>\n<\/blockquote>\n<p>The Release side of things will also look very familiar.&nbsp; If there are any waiters in the queue, we dequeue one and complete its TaskCompletionSource&lt;bool&gt;, but we do so outside of the lock, for the same reason we did so with AsyncAutoResetEvent.&nbsp; If there aren&rsquo;t any waiters, then we simply increment the count.<\/p>\n<blockquote>\n<p><font size=\"2\" face=\"Consolas\"><font color=\"#0000ff\">public void<\/font> Release()         <br>{         <br>&nbsp;&nbsp;&nbsp; <font color=\"#4bacc6\">TaskCompletionSource<\/font>&lt;<font color=\"#0000ff\">bool<\/font>&gt; toRelease = <font color=\"#0000ff\">null<\/font>;         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">lock<\/font> (m_waiters)         <br>&nbsp;&nbsp;&nbsp; {         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">if<\/font> (m_waiters.Count &gt; 0)         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toRelease = m_waiters.Dequeue();         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">else<\/font>         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ++m_currentCount;         <br>&nbsp;&nbsp;&nbsp; }         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">if<\/font> (toRelease != <font color=\"#0000ff\">null<\/font>)         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toRelease.SetResult(<font color=\"#0000ff\">true<\/font>);         <br>}<\/font><\/p>\n<\/blockquote>\n<p>Next time, we&rsquo;ll take a look at how to use such an AsyncSemaphore to implement a <a href=\"https:\/\/blogs.msdn.com\/b\/pfxteam\/archive\/2012\/02\/12\/10266988.aspx\">scoped mutual exclusion locking mechanism<\/a>.<\/p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In my last few posts, I covered building an AsyncManualResetEvent, an AsyncAutoResetEvent, an AsyncCountdownEvent, and an AsyncBarrier.&nbsp; In this post, I&rsquo;ll cover building an AsyncSemaphore class. Semaphores have a wide range of applicability.&nbsp; They&rsquo;re great for throttling, for protected access to a limited set of resources, and more.&nbsp; There are two public semaphore types in [&hellip;]<\/p>\n","protected":false},"author":360,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[7908],"tags":[7925,36,7916,7909,7912],"class_list":["post-56019","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-pfxteam","tag-net-4-5","tag-async","tag-coordination-data-structures","tag-parallel-extensions","tag-task-parallel-library"],"acf":[],"blog_post_summary":"<p>In my last few posts, I covered building an AsyncManualResetEvent, an AsyncAutoResetEvent, an AsyncCountdownEvent, and an AsyncBarrier.&nbsp; In this post, I&rsquo;ll cover building an AsyncSemaphore class. Semaphores have a wide range of applicability.&nbsp; They&rsquo;re great for throttling, for protected access to a limited set of resources, and more.&nbsp; There are two public semaphore types in [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/56019","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/360"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=56019"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/56019\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=56019"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=56019"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=56019"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}