I am setting up separate .NET (C#) tasks to run in parallel in an ASP.NET project (.NET 4.6.2) and want to limit the number of them that can run at once to 10. Most examples I have seen to accomplish this make use of the SemaphoreSlim
and I have set up my logic the same as most of those examples:
// Limit the concurrent number of threads to 10
var throttler = new SemaphoreSlim(10);
// Create a list of tasks to run
var tasks = images.Select(async image =>
{
_logger.Info("WAITING FOR THROTTLER");
await throttler.WaitAsync();
try
{
_logger.Info("ABOUT TO DOWNLOAD IMAGE FROM API A");
var imageData = await DownloadImage(...);
if (imageData != null)
{
_logger.Info("ABOUT TO UPLOAD IMAGE TO API B");
await AddFileAsync(...);
}
}
catch (Exception e)
{
_logger.Error("Failed on UploadImages: " + e.Message);
}
finally
{
throttler.Release();// Always release the semaphore when done
}
});
await Task.WhenAll(tasks);// Now we actually run the tasks
For context, this code runs as fire-and-forget and is triggered as part of a web request. Each task downloads image data from an API (A) and then uploads that data to a different API (B) via HttpClient
.
Testing this with 12 images, sometimes it works fine. But, there is an intermittent issue where sometimes 2 images never make it through, no error is thrown or returned from the API calls. I placed the logs in to narrow it down and found that after the first 10 process, the last 2 just never get passed the log: "WAITING FOR THROTTLER" so it seems to be stuck on waiting for the semaphore to release?
Output logs from one of these issue instances:
2023-03-15 18:33:17,049 [41] INFO MyService WAITING FOR THROTTLER
2023-03-15 18:33:17,049 [41] INFO MyService ABOUT TO DOWNLOAD IMAGE FROM API A
2023-03-15 18:33:17,050 [41] INFO MyService WAITING FOR THROTTLER
2023-03-15 18:33:17,050 [41] INFO MyService ABOUT TO DOWNLOAD IMAGE FROM API A
2023-03-15 18:33:17,051 [41] INFO MyService WAITING FOR THROTTLER
2023-03-15 18:33:17,051 [41] INFO MyService ABOUT TO DOWNLOAD IMAGE FROM API A
2023-03-15 18:33:17,053 [41] INFO MyService WAITING FOR THROTTLER
2023-03-15 18:33:17,053 [41] INFO MyService ABOUT TO DOWNLOAD IMAGE FROM API A
2023-03-15 18:33:17,054 [41] INFO MyService WAITING FOR THROTTLER
2023-03-15 18:33:17,054 [41] INFO MyService ABOUT TO DOWNLOAD IMAGE FROM API A
2023-03-15 18:33:17,055 [41] INFO MyService WAITING FOR THROTTLER
2023-03-15 18:33:17,055 [41] INFO MyService ABOUT TO DOWNLOAD IMAGE FROM API A
2023-03-15 18:33:17,056 [41] INFO MyService WAITING FOR THROTTLER
2023-03-15 18:33:17,056 [41] INFO MyService ABOUT TO DOWNLOAD IMAGE FROM API A
2023-03-15 18:33:17,057 [41] INFO MyService WAITING FOR THROTTLER
2023-03-15 18:33:17,058 [41] INFO MyService ABOUT TO DOWNLOAD IMAGE FROM API A
2023-03-15 18:33:17,059 [41] INFO MyService WAITING FOR THROTTLER
2023-03-15 18:33:17,059 [41] INFO MyService ABOUT TO DOWNLOAD IMAGE FROM API A
2023-03-15 18:33:17,059 [41] INFO MyService WAITING FOR THROTTLER
2023-03-15 18:33:17,059 [41] INFO MyService ABOUT TO DOWNLOAD IMAGE FROM API A
SEE HERE TWO LINES BELOW
2023-03-15 18:33:17,061 [41] INFO MyService WAITING FOR THROTTLER
2023-03-15 18:33:17,061 [41] INFO MyService WAITING FOR THROTTLER
SEE HERE TWO LINES ABOVE
2023-03-15 18:33:17,996 [74] INFO MyService ABOUT TO UPLOAD IMAGE TO API B
2023-03-15 18:33:18,231 [40] INFO MyService ABOUT TO UPLOAD IMAGE TO API B
2023-03-15 18:33:18,241 [40] INFO MyService ABOUT TO UPLOAD IMAGE TO API B
2023-03-15 18:33:18,247 [51] INFO MyService ABOUT TO UPLOAD IMAGE TO API B
2023-03-15 18:33:18,253 [51] INFO MyService ABOUT TO UPLOAD IMAGE TO API B
2023-03-15 18:33:18,259 [51] INFO MyService ABOUT TO UPLOAD IMAGE TO API B
2023-03-15 18:33:18,265 [79] INFO MyService ABOUT TO UPLOAD IMAGE TO API B
2023-03-15 18:33:18,271 [79] INFO MyService ABOUT TO UPLOAD IMAGE TO API B
2023-03-15 18:33:18,277 [40] INFO MyService ABOUT TO UPLOAD IMAGE TO API B
2023-03-15 18:33:18,309 [74] INFO MyService ABOUT TO UPLOAD IMAGE TO API B
I have outlined the two lines in the logs that suggest to me the issue is related to the semaphore. There are 10 logs for calling "DOWNLOAD" and "UPLOAD", but all 12 show "WAITING FOR THROTTLER" So you can see that two of the tasks never reach the point of calling the API and are stuck waiting on the semaphore.
I wonder why this happens though especially when it works sometimes, seems to be some sort of timing issue?
*I'll also note that using Parallel.ForEachAsync
is not an option as this project is running on .NET 4.6.2 and upgrading is not an option right now.