A Generic Timeout Helper
Another post that hopefully may come in handy to someone!
Some APIs, but not all, notably older ones, allow passing a timeout value for the maximum allowed duration of its completion. What for those that don’t have such parameter? Well, I wrote one helper method to assist in those cases. All it takes is an action and the desired time slot, and it will throw an exception if the action takes longer than it should, leaving it for us to deal with the situation. Here is the code:
public static class RestrainedExtensions {
private static void ExecuteRestrainedCommon(Task actionTask, TimeSpan maxDelay)
{
var delayTask = Task.Delay(maxDelay);
var finishedTaskIndex = Task.WaitAny(actionTask, delayTask);
if (finishedTaskIndex != 0)
{
throw new TimeoutException("Action did not finish in the desired time slot.");
}
}
public static void ExecuteRestrained<T>(Func<T> func, TimeSpan maxDelay)
{
var executionTask = Task.Run(() =>
{
func();
});
ExecuteRestrainedCommon(executionTask, maxDelay);
}
public static void ExecuteRestrained(Action action, TimeSpan maxDelay)
{
var executionTask = Task.Run(() =>
{
action();
});
ExecuteRestrainedCommon(executionTask, maxDelay);
} }
As you can see, the RestrainedExtensions class offers two overloads of the ExecuteRestrained method, this is to make our lives easier when we want to execute some method that has a return type or otherwise. Essentially what we do is, we create two tasks, one which just executes a delay and the other which tries to execute our action, and we wait for the first to complete; if this wasn’t our action, then we throw an TimeoutException.
An alternate implementation of ExecuteRestrained, making use of the new WaitAsync method, could be:
private static void ExecuteRestrainedCommon(Task actionTask, TimeSpan maxDelay)
{
actionTask.WaitAsync(maxDelay).ConfigureAwait(false).GetAwaiter().GetResult();
}
If the action to be awaited does not run in the desired time slot, WaitAsync throws a TimeoutException.
Here’s a simple usage:
RestrainedExtensions.ExecuteRestrained(Console.ReadLine, TimeSpan.FromSeconds(3));
As you can imagine, this gives the user 3 seconds to enter something at the console before throwing an exception. Simple enough, probably has something to it, but surely can be used in most cases! As always, feel free to share your thoughts!