async 8: creating combinators

These samples use the concepts from the last sample to handle situations where an async operation does not complete as quickly as users might expect.  The first sample shows you how to handle the situation where an async operation should be cancelled because it hasn’t completed in time:

        public async void AsyncTimeoutAfter()
{
try
{
int result = await TimeoutAfter(LongRunningOperation(), 3000);
Console.WriteLine("Operation completed successfully. Result is {0}.",
result);
}
catch (TimeoutException)
{
Console.WriteLine("Timeout - operation took longer than 3 seconds.");
}
}

publicstatic async Task<T> TimeoutAfter<T>(Task<T> task, int delay)
{
await TaskEx.WhenAny(task, TaskEx.Delay(delay));

if (!task.IsCompleted)
thrownew TimeoutException("Timeout hit.");

return await task;
}

public async Task<int> LongRunningOperation()
{
Console.WriteLine("Attempting long-running operation...");

await new SynchronizationContext().SwitchTo();

// Simulate a process that takes between 2 and 4 seconds to complete.
Random r = new Random();
await TaskEx.Delay(r.Next(2000, 4000));

return 123;
}

The interesting new code is in TimeoutAfter(). Notice that it awaits the completion of the long running task, but only up to the delay time of 3 sec. If the operation has not completed, TimeoutAfter throws a TimeoutException.

The other situation is that you may want to restart an async operation that does not complete in time:

        public async void AsyncRetryOnFault()
{
try
{
int result = await RetryOnFault(() => TimeoutAfter(LongRunningOperation(),
3000), 3);
Console.WriteLine("Operation completed successfully. Result is {0}.",
result);
}
catch (TimeoutException)
{
Console.WriteLine("Operation timed out 3 times. Giving up.");
}
}

publicstatic async Task<T> RetryOnFault<T>(Func<Task<T>> function, int maxTries)
{
for (int i = 0; i < maxTries; i++)
{
try { return await function(); }
catch (Exception) { if (i == maxTries - 1) throw; }
}
returndefault(T);
}

public async Task<int> LongRunningOperation()
{
Console.WriteLine("Attempting long-running operation...");

await new SynchronizationContext().SwitchTo();

// Simulate a process that takes between 2 and 4 seconds to complete.
Random r = new Random();
await TaskEx.Delay(r.Next(2000, 4000));

return 123;
}

OK, I’ll admit I’m less than thrilled with using the catch exception / throw logic for flow control. However, it’s a quick sample, so we’ll run with it.  RetryOnFault() starts the async operation. If it does not complete, it increments the number of times it’s been tried. If the operation has been tried enough times, this code assumes it won’t complete. The TimeoutException from the final attempt gets rethrown.

Again, the key is to start the task and await up to a prescribed maximum time. If the task has not completed, take some corrective action. That corrective action may be to restart, or cancel the task.

Created: 3/31/2011 12:03:18 PM

Current Projects

I create content for .NET Core. My work appears in the .NET Core documentation site. I'm primarily responsible for the section that will help you learn C#.

All of these projects are Open Source (using the Creative Commons license for content, and the MIT license for code). If you would like to contribute, visit our GitHub Repository. Or, if you have questions, comments, or ideas for improvement, please create an issue for us.

I'm also the president of Humanitarian Toolbox. We build Open Source software that supports Humanitarian Disaster Relief efforts. We'd appreciate any help you can give to our projects. Look at our GitHub home page to see a list of our current projects. See what interests you, and dive in.

Or, if you have a group of volunteers, talk to us about hosting a codeathon event.