4
\$\begingroup\$

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();
\$\endgroup\$
  • 1
    \$\begingroup\$ In TimeoutAfter() you can remove the else and just return false. \$\endgroup\$ – Heslacher Nov 26 '14 at 9:59
  • \$\begingroup\$ Why do you await the task again after it completed in the WhenAny? \$\endgroup\$ – Dave Van den Eynde Jun 20 '17 at 12:12
  • \$\begingroup\$ And why do you care to cancel that delay task you have no intention of awaiting again? \$\endgroup\$ – Dave Van den Eynde Jun 20 '17 at 12:12
2
\$\begingroup\$
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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.