Asynchronously wait for a task to complete and do some async action while waiting

Posted on

Problem

I have a long-running task. My goal is to create a method that will allow me to:

  1. Asynchronously wait for this task to complete
  2. While waiting on a task, do some async action once in a while. This
    ‘action’ basically tells some remote service that task is not dead
    and still executing.

I’ve written the following code to solve this problem. Please have a look and share your thoughts!

public delegate Task AsyncAction();

public static class TaskExtensions
{
    public static async Task<bool> TimeoutAfter(this Task task, TimeSpan timeout)
    {
        var timeoutCancellationTokenSource = new CancellationTokenSource();

        var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)).ConfigureAwait(false);
        if (completedTask == task)
        {
            timeoutCancellationTokenSource.Cancel();
            await task.ConfigureAwait(false);
            return true;
        }
        else
        {
            return false;
        }
    }

    public static async Task AwaitWithTimeoutCallback(this Task task, TimeSpan timeout, AsyncAction onTimeout)
    {
        while (true)
        {
            var completed = await task.TimeoutAfter(timeout).ConfigureAwait(false);
            if (completed)
            {
                break;
            }

            await onTimeout().ConfigureAwait(false);
        }

        await task.ConfigureAwait(false);
    }
}

Usage:

var task = Task.Delay(TimeSpan.FromSeconds(15));

task.AwaitWithTimeoutCallback(TimeSpan.FromSeconds(5), () =>
{
    Console.WriteLine("Waiting...");
    return Task.FromResult(true);
})
.Wait();

Solution

public delegate Task AsyncAction();

Instead of creating a custom delegate type, you could just reuse Func<Task> (even though logically, it is an action and not a function).


return Task.FromResult(true);

Consider adding an overload that takes a non-async onTimeout delegate, so that this confusing code wasn’t required.

Or, if you do something like this often, have some TaskEx.Completed, or something like that.


Otherwise, good job. I especially like that you remembered to use ConfigureAwait(false) everywhere.

Leave a Reply

Your email address will not be published. Required fields are marked *