I recently came across an example of throttling threads for async/await calls. After analyzing and playing with the code on my machine, I came up with a slightly different way of doing the same thing. What I'm uncertain about is wether what is happening under the hood is pretty much the same or if there are any subtle differences worth noting?
Here's the code based on the original example:
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(5);
public async Task CallThrottledTasks()
{
var tasks = new List<Task>();
for (int count = 1; count <= 20; count++)
{
await _semaphore.WaitAsync();
tasks.Add(Task.Run(async () =>
{
try
{
int result = await LongRunningTask();
Debug.Print(result.ToString());
}
finally
{
_semaphore.Release();
}
}));
}
await Task.WhenAll(tasks);
Debug.Print("Finished CallThrottledTasks");
}
And here's my take on the same code:
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(5);
public async Task CallThrottledTasks()
{
var tasks = new List<Task>();
for (int count = 1; count <= 20; count++)
{
await _semaphore.WaitAsync();
tasks.Add(LongRunningTask().ContinueWith(t =>
{
try
{
int result = t.Result;
Debug.Print(result.ToString());
}
finally
{
_semaphore.Release();
}
}));
}
await Task.WhenAll(tasks);
Debug.Print("Finished CallThrottledTasks");
}
I'm probably way off, but It seems like the Task.Run approach is creating a task to run LongRunningTask() and then adds a continuation to print the result whereas my approach bypasses the task created by Task.Run and is a bit leaner as a result. Is this accurate or am I way off base here?
It's not much leaner, just a little bit. Usually, I avoid ContinueWith
in async
code because await
is cleaner and has more async
-friendly default semantics. Optimize for developer time first, then optimize for other considerations.
Your code does change the semantics slightly: in the original code, LongRunningTask
was executed from a thread pool context, and in your code it's executed from whatever the CallThrottledTasks
context is. Also, your code won't propagate exceptions from LongRunningTask
cleanly; Task<T>.Result
will wrap exceptions in AggregateException
, while await
will not do any wrapping.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With