Asynchronous programming is a bit nightmarish, but hopefully this sheds some light upon how you can leverage certain delegate types. Take this example, where you want an abstraction layer for calling a block of code up to a set number of attempts until it succeeds, otherwise having it delegate back the exception you’ve obtained on the very last attempt:
class Program { static void Main(string[] args) { // Since this is a console application, I just ran everything inside of an async task. Task.Run(async () => { bool throwException = true; var firstResult = await AttemptBlock.Run(async () => { Console.WriteLine("X called."); if (throwException) { throwException = false; throw new Exception(); } await AttemptBlock.Run(async () => { Console.WriteLine("Y called."); await Z(); Console.WriteLine("Y finished."); }, 3, async (exception) => { await Task.Run(() => { Console.WriteLine("Y exception handler called."); }); }); Console.WriteLine("X finished."); }, 3, async (exception) => { await Task.Run(() => { Console.WriteLine("X exception handler called."); }); }); Console.WriteLine("Finished the first block with {0}.", firstResult); var secondResult = await AttemptBlock.Run(async () => { Console.WriteLine("O called."); await Z(); throw new Exception(); }, 3, async (exception) => { await Task.Run(() => { Console.WriteLine("O exception handler called."); throw new Exception(); }); }); Console.WriteLine("Finished the second block with {0}.", secondResult); }); Console.ReadLine(); } private async static Task<bool> Z() { Console.WriteLine("Z called."); await Task.Delay(1000); Console.WriteLine("Z finished."); return true; } } class AttemptBlock { public async static Task<bool> Run(Func<Task> function, int attempts, Func<Exception, Task> handler = null) { while (attempts > 0) { var candidate = (Exception)null; try { await function(); return true; } // If this try-catch block was not here, the function could deadlock if it threw an exception. catch (Exception exception) { candidate = exception; } attempts--; if (attempts <= 0 && handler != null && candidate != null) try { await handler(candidate); } catch { } // If this try-catch block was not here, the handler could deadlock if it threw an exception. } return false; } }
The output is as follows:
X called.
X called.
Y called.
Z called.
Z finished.
Y finished.
X finished.
Finished the first block with True.
O called.
Z called.
Z finished.
O called.
Z called.
Z finished.
O called.
Z called.
Z finished.
O exception handler called.
Finished the second block with False.
You may also want to check out this post on Synchronous and Asynchronous Delegate Types.