In this tip, you will see a couple of examples for how to add time out for functions that may just hang.
Introduction
When communicating with external services, some functions may just "hang": attempting to establish a TCP connection when the network or the other device is down takes 21s till you get a failure message. When the device is in your local network, you can normally be sure to get the connection within less than a second or not at all, and there is no need to waste so much time.
So it’s appropriate to add some time out. There are a couple of examples how to achieve that, but they require the repetition of (short) boiler plate code.
Thanks to generics and functional features of C# that can be encapsulated.
The Code
We need three items:
- A function to execute
- The timeout for that function
- A function to execute in case of the timeout
The first step is to start a Task
with the function. Task
has a Wait(TimeSpan timeout)
function, which returns true
if the task completed in time. Here, we can return the Result
property of the task. Otherwise, we execute the onTimeout
function and return its result.
The code is simple:
public static T Execute(Func<t> function, TimeSpan timeout, Func<t> onTimeout)
{
Task<t> task = Task.Run(function);
if (task.Wait(timeout))
{
return task.Result;
}
else
{
return onTimeout();
}
}
Instead of returning a default value, you may also throw an exception in the onTimeout
function.
Unfortunately, in C# Function
is a concept different from Action
. Hence, I added an overload for Action
, too.
Limitation: Cancellation
As you may find out, the original function stays alive after timeout - it continues to hang till it has run to completion. With the example of the TCP connection, that does not matter. If it consumes valuable ressources like CPU, that's bad.
Task.Run
has an overload which takes a CancellationToken
. But that does not help when the original function does not support cancellation (it must check the IsCancellationRequested
property of the CancellationToken
). If it does, you do not need this code, just supply a new CancellationTokenSource(timeout).Token
to it.
This is a limitation for the use of this code.
Using the Code
Let’s add some examples.
I created a function which sleeps for a day before returning true
– that corresponds to the „hanging“ function.
In the default value example, I set up 3 variables for the 3 items:
Func<bool> theFunction = DoSomething;
TimeSpan timeout = TimeSpan.FromSeconds(3);
Func<bool> onTimeout = () =>
{
return false;
};
and then execute it:
bool success = TimedExecution<bool>.Execute(theFunction, timeout, onTimeout);
but you could write it more concisely, too:
bool success = TimedExecution<bool>.Execute(DoSomething, TimeSpan.FromSeconds(3), () => false);
The exceptional example works analogous. Here, the onTimeout
function is set up to throw an exception:
Func<bool> onTimeout = () =>
{
throw new TimeoutException("The function was to slow.");
};
In the example code, I added [TestMethod]
attributes, which allow for executing and debugging these example functions from Visual Studio.
Points of Interest
You can now adjust the functions to your typical needs. Example, you might log the timeout directly in the Execute
function. Or you only need a function which always throws a TimeoutException
in case of timeout. Or whatever fits your requirements best. Now it's centralized and you can change the behavior at a single point.
History
- 18th March, 2020: First version
- 20th March, 2020: Added more info on the background, and the limitations for the use of the code