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;
}
}
}
}