# WaitAsync

.NET 6 has a new method added to `System.Threading.Tasks.Task` to wait for a task completion or timeout. `WaitAsync` method completes with the result of the task it is invoked on, or throws a timeout exception when the given timeout reached or throws a `TaskCancelledException` if the given cancellation token is set to cancelled state.

The method's primary use-case is to add cancellation or timeout ability for async methods, that inherently don't provide such capability. One use-case could be unit tests, where we explicitly want to time out and fail a test if the method invoked as the *system under test* is an async operation without a timeout overload. This way we can let other tests to be executed in the test suite.

Previously a poor man (woman)'s implementation for such timeout could look as:

```csharp
public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout)
{
    if (task.IsCompleted)
        return await task;
    var cts = new CancellationTokenSource();
    if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token)))
    {
        cts.Cancel();
        return await task;
    }
    else
    {
        throw new TimeoutException();
    }
}
```

In the above example, a developer has to take care of the extra cancellation of the `CancellationTokenSource`, even if the task completes successfully. Otherwise for non-timeout cases it could leave the delay task still being executed until the timeout expires. Having many non-expired timeout's concurrently would degrade the performance of the timer that is used by the `Task.Delay`. Hence it is suggested to proactively cancel the delay task.

The above code becomes now legacy. The new `WaitAsync` API may replace it, which has three overloads, with the most complex signature:

```csharp
Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken);
```

The API methods has three overloads:
- one that takes an input parameter `TimeSpan` for the timeout time
- one that takes a `CancellationToken`
- one that takes both of them

> Note, that the API returns the underlying task if it completes synchronously, otherwise it may return a new task with the result of the underlying task. Hence we shall use reference equality checks on the returned task object.

## An Example

For example can use the above `WaitAsync` method with a timespan overload:

```csharp
using System;
using System.Threading.Tasks;

async Task LongRunningWorkAsync()
{
    while (true)
        await Task.Delay(100);
}

await LongRunningWorkAsync().WaitAsync(TimeSpan.FromSeconds(2));
```

`LongRunningWorkAsync()` is being awaited with a timeout of 2 seconds. Because the await method never returns, the application will throw a timeout exception after 2 seconds.

## Negative Infinity

An interesting aspect of the API is using it with -1 milliseconds as the timeout period. This value has a special meaning of *negative infinity*. That means the `WaitAsync` method will disregard the timeout parameter, and only completes if the awaited task compeletes or cancellation triggered.

```csharp
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(2));
await LongRunningWorkAsync().WaitAsync(TimeSpan.FromMilliseconds(-1), cts.Token);
```

The code above would throw a `TaskCanceledException` after two seconds.