C# – Use SemaphoreSlim for throttling threads

When you have multiple threads trying to do work at the same time, and you want to throttle how many of them are actually executing, you can use SemaphoreSlim.

//Step 1 - create the semaphore //Specifying how many threads //to execute concurrently var semaphore = new SemaphoreSlim(numThreadsToRunConcurrently); //Step 2 - In the code where you're executing the work //await the semaphore await semaphore.WaitAsync(); //Step 3 - release when finished semaphore.Release();

Example – a busy grocery store

Grocery stores have a limited number of checkout lanes open.

Let’s say the grocery store has two lanes open, and there are currently 10 shoppers who all want to checkout at the same time.

Because only two lanes are open, only two shoppers can checkout. The remaining eight have to wait.

The manager sees there is a big backlog forming, so they open up a new lane, which can immediately start checking out one of the waiting shoppers.

Grocery Store with multiple shoppers checking out using limited lanes

Code

Initialize SemaphoreSlim

In our example, the grocery store initially has two lanes open.

checkoutLanes = new SemaphoreSlim(2);

Send shoppers to checkout

Every time we click “Send to checkout,” for every shopper we are calling Checkout(Shopper) in a new Task.

private void SendShoppersToCheckout(int numberOfShoppers) { for (int i = 1; i <= numberOfShoppers; i++) { var shopper = new Shopper() { Items = random.Next(5, 15), Number = totalShoppers++ }; Task.Run(async () => { await Checkout(shopper); }); } }

Throttled checkout

When a shopper goes to checkout, they must first check if a checkout lane is open by calling await checkoutLanes.WaitAsync().

If there are no lanes open, the shopper thread will await here (non-blocking).

If lanes are open, they continue forward with their checkout.

Once a shopper is finished, they exit the lane by calling Release(), which allows another awaiting shopper to continue forward.

private async Task Checkout(Shopper s) { Log($"Shopper {s.Number} is waiting to checkout with {s.Items} item(s)"); await checkoutLanes.WaitAsync(); var timeToCheckout = TimeSpan.FromSeconds(1 * s.Items); Log($"Shopper {s.Number} is now checking out. This will take {timeToCheckout.TotalSeconds} seconds"); await Task.Delay(timeToCheckout); Log($"Shopper {s.Number} finished checking out"); checkoutLanes.Release(); }

Opening more lanes

To open a new lane, the manager would call Release(1).

We started with two open lanes, and now have three. Going forward, three shoppers will be able to checkout at the same time.

private void btnOpenNewLane_Click(object sender, EventArgs e) { Log("Opening a new lane!"); checkoutLanes.Release(1); numLanesOpen.Value++; }

Leave a Comment