Skip to main content

5 useful extensions for Task<T> in .NET

4/10/2023
3 minute read

In this short blog post, I will show you 5 useful extensions for Task in .NET. We will build them as extension methods, so there are easy to use. On top, I will show a small example of how to use them. So let's go!

1. Fire and forget#

Sometimes you want to fire and forget a task. This means that you want to start a task but you don't want to wait for it to finish. This is useful when you want to start a task but you don't care about the result (non-critical tasks). For example when you want to start a task that sends an email. You don't want to wait for the email to be sent before you can continue with your code. So you can use the FireAndForget extension method to start the task and forget about it. Optionally you can pass an error handler to the method. This error handler will be called when the task throws an exception.

csharp
public static void FireAndForget(
  this Task task,
  Action<Exception> errorHandler = null)
{
    task.ContinueWith(t =>
    {
        if (t.IsFaulted && errorHandler != null)
            errorHandler(t.Exception);
    }, TaskContinuationOptions.OnlyOnFaulted);
}

Usage:

csharp
SendEmailAsync().FireAndForget(errorHandler => Console.WriteLine(errorHandler.Message));

2. Retry#

If you want to retry a task a specific number of times, you can use the Retry extension method. This method will retry the task until it succeeds or the maximum number of retries is reached. You can pass a delay between retries. This delay will be used between each retry.

csharp
public static async Task<TResult> Retry<TResult>(this Func<Task<TResult>> taskFactory, int maxRetries, TimeSpan delay)
{
    for (int i = 0; i < maxRetries; i++)
    {
        try
        {
            return await taskFactory().ConfigureAwait(false);
        }
        catch
        {
            if (i == maxRetries - 1)
                throw;
            await Task.Delay(delay).ConfigureAwait(false);
        }
    }

    return default(TResult); // Should not be reached
}

Usage:

csharp
var result = await (() => GetResultAsync()).Retry(3, TimeSpan.FromSeconds(1));

3. OnFailure#

Executes a callback function when a Task encounters an exception.

csharp
public static async Task OnFailure(this Task task, Action<Exception> onFailure)
{
    try
    {
        await task.ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        onFailure(ex);
    }
}

Usage:

csharp
await GetResultAsync().OnFailure(ex => Console.WriteLine(ex.Message));

4. Timeout#

Sometimes you want to set a timeout for a task. This is useful when you want to prevent a task from running for too long. You can use the Timeout extension method to set a timeout for a task. If the task takes longer than the timeout the task will be cancelled.

csharp
public static async Task WithTimeout(this Task task, TimeSpan timeout)
{
    var delayTask = Task.Delay(timeout);
    var completedTask = await Task.WhenAny(task, delayTask).ConfigureAwait(false);
    if (completedTask == delayTask)
        throw new TimeoutException();

    await task;
}

Usage:

csharp
await GetResultAsync().WithTimeout(TimeSpan.FromSeconds(1));

Note: Since .NET 6 you can use WaitAsync.

5. Fallback#

Sometimes you want to use a fallback value when a task fails. You can use the Fallback extension method to use a fallback value when a task fails.

csharp
public static async Task<TResult> Fallback<TResult>(this Task<TResult> task, TResult fallbackValue)
{
    try
    {
        return await task.ConfigureAwait(false);
    }
    catch
    {
        return fallbackValue;
    }
}

Usage:

csharp
var result = await GetResultAsync().Fallback("fallback");

Conclusion#

There we have it: Five useful extensions for Task. Of course, you can also make them work for ValueTask if you want to. If you have more, please let me know!

An error has occurred. This application may no longer respond until reloaded.Reload x