Leveraging Func<Task> and Getting Funky

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.