I was wondering which approach would be more efficient in terms of memory and resource usage in general.
Particular with approach #1, I'm having a hard time visualizing how the task objects will be created and threads spun up? Could someone please explain what goes on under the covers in detail as an aside?
I'd like to use #1 if there's no difference between the two (want to avoid bubbling up async). With #2, I understand the compiler will generate a state machine underneath and yield return. OTOH, #1 seems recursive conceptually but will it be recursive in the traditional sense as in one stack-frame waiting on the other?
Approach #1:
internal static Task ExecuteAsyncWithRetry(Func<Task> methodToExecute, Func<bool> shouldRetry)
{
var tcs = new TaskCompletionSource<object>();
try
{
return methodToExecute().ContinueWith<Task>((t) =>
{
if (t.IsFaulted || t.IsCanceled)
{
if (shouldRetry())
{
return ExecuteAsyncWithRetry(methodToExecute, shouldRetry);
}
else
{
tcs.SetException(t.Exception);
}
}
else
{
tcs.SetResult(null);
}
return tcs.Task;
}, TaskContinuationOptions.ExecuteSynchronously).Unwrap();
}
catch(Exception ex)
{
tcs.SetException(ex);
}
return tcs.Task;
}
Approach #2 (ignore the difference in exception propagation between the two):
internal static async Task ExecuteWithRetry(Func<Task> methodToExecute, Func<bool> shouldRetry)
{
while (true)
{
try
{
await methodToExecute();
}
catch(Exception ex)
{
if(!shouldRetry())
{
throw;
}
}
}
}
Asynchronous programming can in some cases help with performance by parallelizing a task. But, that is not its main benefit in day to day development. Instead, the main benefit comes from making our code more scalable. The scalability feature of a system relates to how it handles a growing amount of work.
The main benefits of asynchronous programming using async / await include the following: Increase the performance and responsiveness of your application, particularly when you have long-running operations that do not require to block the execution.
No problem, just make a loop and call this function with an await: [code] for (int i = pendingList. Count - 1; i >= 0; i--)
For methods other than event handlers that don't return a value, you should return a Task instead, because an async method that returns void can't be awaited. Any caller of such a method must continue to completion without waiting for the called async method to finish.
Besides different exceptions and cancellation propagation, there's another major difference.
In the 1st case, your continuation runs on the same thread where the task has completed, because of TaskContinuationOptions.ExecuteSynchronously
.
In the 2nd case, it will be run on the original synchronization context (if methodToExecute
was called on a thread with synchronization context).
Even though the 1st approach might be more efficient, it also might be hard to understand (especially when your or someone else returns to it in a year from now).
I'd follow the KISS principle and stick with the second one, with one amendment:
await methodToExecute().ConfigureAwait(false);
Updated to address the comment:
"OTOH, #1 seems recursive conceptually but will it be recursive in the traditional sense as in one stack-frame waiting on the other?"
For #1, whether it will happen recursively on the same stack frame, or asynchronously on a different stack frame, totally depends on what's going inside methodToExecute
. In most cases, there will be no traditional recursion, if you use some naturally async APIs inside methodToExecute
. E.g., HttpClient.GetStringAsync
completes on a random IOCP pool thread, and Task.Delay
completes on a random worker pool thread.
However, even an async API may complete synchronously on the same thread (e.g. MemoryStream.ReadAsync
or Task.Delay(0)
), in which case there will be recursion.
Or, using TaskCompletionSource.SetResult
inside methodToExecute
may also trigger a synchronous continuation.
If you really want to avoid any possibility of recursion, call methodToExecute
via Task.Run
(or Task.Factory.StartNew
/ Task.Unwrap
). Or, better yet, remove TaskContinuationOptions.ExecuteSynchronously
.
For #2, the same scenario is also possible, even when there's a synchronization context on the initial thread.
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