Asynchronous Task.Whenall with Timeout

Asynchronous Task.WhenAll with timeout

You could combine the resulting Task with a Task.Delay() using Task.WhenAny():

await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(timeout));

If you want to harvest completed tasks in case of a timeout:

var completedResults =
tasks
.Where(t => t.Status == TaskStatus.RanToCompletion)
.Select(t => t.Result)
.ToList();

Add timeout to complex Task.WhenAll

it won't compile

This is because Task.Delay returns a Task. There's no value or exception there. So it can't be directly combined with a sequence of ResultOrException<T>.

You'll need to decide how to report timeouts to your caller. If you want the Task<ResultOrException<T>[]> to fault, then you can do this:

public async Task<ResultOrException<T>[]> WhenAllOrException<T>(IEnumerable<Task<T>> tasks)
{
var resultOrExceptions = Task.WhenAll(
tasks.Select(task => ...)
);
var delayTask = Task.Delay(2000);
var completedTask = await Task.WhenAny(resultOrExceptions, delayTask);
if (completedTask == delayTask)
throw new TimeoutException();
return await resultOrExceptions;
}

Alternatively, if you want to return an array ResultOrException<T>, each one faulted with a timeout, then you can do this:

public async Task<ResultOrException<T>[]> WhenAllOrException<T>(IEnumerable<Task<T>> tasks)
{
var resultOrExceptionTasks = tasks.Select(task => ...)
.ToArray();
var resultOrExceptions = Task.WhenAll(resultOrExceptionTasks);
var delayTask = Task.Delay(2000);
var completedTask = await Task.WhenAny(resultOrExceptions, delayTask);
if (completedTask == delayTask)
return Enumerable.Repeat(new ResultOrException<T>(new TimeoutException()), resultOrExceptionTasks.Length).ToArray();
return await resultOrExceptions;
}

Or, if you want to return the results that made it in time, and only return timeout exceptions for those that didn't, then you want to move the WhenAny inside the WhenAll:

public Task<ResultOrException<T>[]> WhenAllOrException<T>(IEnumerable<Task<T>> tasks)
{
var delayTask = Task.Delay(2000);
return Task.WhenAll(tasks.Select(WithTimeout));

async Task<ResultOrException<T>> WithTimeout(Task<T> task)
{
var completedTask = await Task.WhenAny(task, delayTask);
if (completedTask == delayTask)
return new ResultOrException<T>(new TimeoutException());
try
{
return new ResultOrException<T>(await task);
}
catch (Exception ex)
{
return new ResultOrException<T>(ex);
}
}
}

Side note: you should always pass a TaskScheduler to ContinueWith. Also, I have a Try implementation that may be useful.

Asynchronously wait for TaskT to complete with timeout

How about this:

int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
// task completed within timeout
} else {
// timeout logic
}

And here's a great blog post "Crafting a Task.TimeoutAfter Method" (from MS Parallel Library team) with more info on this sort of thing.

Addition: at the request of a comment on my answer, here is an expanded solution that includes cancellation handling. Note that passing cancellation to the task and the timer means that there are multiple ways cancellation can be experienced in your code, and you should be sure to test for and be confident you properly handle all of them. Don't leave to chance various combinations and hope your computer does the right thing at runtime.

int timeout = 1000;
var task = SomeOperationAsync(cancellationToken);
if (await Task.WhenAny(task, Task.Delay(timeout, cancellationToken)) == task)
{
// Task completed within timeout.
// Consider that the task may have faulted or been canceled.
// We re-await the task so that any exceptions/cancellation is rethrown.
await task;

}
else
{
// timeout/cancellation logic
}

Handle tasks which complete after Task.WhenAll().Wait() specified timeout

In the end with help of the user who removed his answer, I ended up with this solution:

private const int TimeoutInSeconds = 5;
private static void Main(string[] args)
{
var tasks = new List<Task>()
{
Task.Run( async() => await Task.Delay(30)),
Task.Run( async() => await Task.Delay(300)),
Task.Run( async() => await Task.Delay(6000)),
Task.Run( async() => await Task.Delay(8000))
};

Task.WhenAll(tasks).Wait(TimeSpan.FromSeconds(TimeoutInSeconds));

var completedTasks = tasks
.Where(t => t.Status == TaskStatus.RanToCompletion).ToList();
var incompleteTasks = tasks
.Where(t => t.Status != TaskStatus.RanToCompletion).ToList();

Task.WhenAll(incompleteTasks)
.ContinueWith(t => { ProcessDelayedTasks(incompleteTasks); });

ProcessCompletedTasks(completedTasks);
Console.ReadKey();
}

private static void ProcessCompletedTasks(IEnumerable<Task> delayedTasks)
{
Console.WriteLine("Processing completed tasks...");
}

private static void ProcessDelayedTasks(IEnumerable<Task> delayedTasks)
{
Console.WriteLine("Processing delayed tasks...");
}

ChromeDriver times out instantly when using Task.Run() with await Task.WhenAll()

I suspect you've written a console application?

public static async Task Main()

Consider this part of the error:

The I/O operation has been aborted because of either a thread exit or
an application request..

awaiting will return execution to the main thread, which has no other work, and will thus end.

When you use Task.WhenAll(pages).Wait(), you force the main thread to continue running until all of your driver tasks have completed.

You can test this using:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class Program
{
public static async Task Main()
{
List<Task> tasks = new List<Task>();
for(var i = 0; i < 10; i++)
{
var captureI = i;
tasks.Add(Task.Run(() => Console.WriteLine(captureI)));
}
Task.WhenAll(tasks).Wait(); // vs await Task.WhenAll(tasks);
}
}

Some solutions are discussed here.

How can I await an enumerable of tasks and stop when a number of tasks have completed?

Even when you have complex logic for cancellation, you want to cancel the underlying tasks. If the underlying tasks are cancelled at the right time, you can use Task.WhenAll in any case.

So breaking down your question, what you're asking is, 'How can I cancel tasks based on the state of other tasks?'. You need to keep a state of number of completed tasks and cancel your tasks based on that state.

If you need to do 'stuff' when tasks complete (like update the state of how many tasks completed), I find continuations to be helpful and quite a clean solution. Example of your usecase:

// n from your question
var n = 4;

// number of tasks currently completed
var tasksCompleted = 0;

// The list of tasks (note it's the continuations in this case)
// You can also keep the continuations and actual tasks in separate lists.
var tasks = new List<Task>();

// delay before cancellation after n tasks completed
var timeAfterNCompleted = TimeSpan.FromMinutes(x);
using var cts = new CancellationTokenSource();

for (int i = 0; i < 10; i++)
{
// Do your work with a passed cancellationtoken you control
var currentTask = DoWorkAsync(i, cts.Token);

// Continuation will update the state of completed tasks
currentTask = currentTask.ContinueWith((t) =>
{
if (t.IsCompletedSuccessfully)
{
var number = Interlocked.Increment(ref tasksCompleted);
if (number == n)
{
// If we passed n tasks completed successfully,
// We'll cancel after the grace period
// Note that this will actually cancel the underlying tasks
// Because we passed the token to the DoWorkAsync method
cts.CancelAfter(timeAfterNCompleted);
}
}
});
tasks.Add(currentTask);
}

await Task.WhenAll(tasks);

// All your tasks have either completed or cancelled here
// Note that in this specific example all tasks will appear
// to have run to completion. That's because we're looking at
// the continuations here. Store continuation and actual task
// in separate lists and you can retrieve the results.
// (Make sure you await the continuations though)


Related Topics



Leave a reply



Submit