1
votes

I am trying to use Task.WhenAll(tasks).Wait(timeout) to wait for tasks to complete and after that process task results.

Consider this example:

var tasks = new List<Task<Foo>>();

tasks.Add(Task.Run(() => GetData1()));
tasks.Add(Task.Run(() => GetData2()));

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

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

// Process completed tasks
// ...

private Foo GetData1()
{
    Thread.Sleep(TimeSpan.FromSeconds(4));
    return new Foo();
}

private Foo GetData2()
{
    Thread.Sleep(TimeSpan.FromSeconds(10));
    // How can I get the result of this task once it completes?
    return new Foo();
}

It is possible that one of these tasks will not complete their execution within 5 second timeout.

Is it possible to somehow process results of the tasks that have completed after specified timeout? Maybe I am not using right approach in this situation?

EDIT:

I am trying to get all task results that managed to complete within specified timeout. There could be the following outcomes after Task.WhenAll(tasks).Wait(TimeSpan.FromSeconds(5)):

  1. First task completes within 5 seconds.
  2. Second task completes within 5 seconds.
  3. Both tasks complete within 5 seconds.
  4. None of the tasks complete within 5 seconds. Is it possible to get task results that haven't completed within 5 seconds, but have completed later, lets say, after 10 seconds?
3
Task.WhenAll(tasks).Wait(TimeSpan.FromSeconds(5)); can be simplified to Task.WaitAll(tasks, TimeSpan.FromSeconds(5)); to give the same results.Scott Chamberlain
Is it not working as expected?Rico Suter

3 Answers

2
votes

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...");
}
0
votes

Instead of Waitall, you probably just want to do some sort of Spin/sleep of 5 seconds and then query the list as you are above.

You should then be able to enumerate again after a few more seconds to see what else has finished.

If performance is a concern, you may want to have additional 'wrapping' to see if All tasks have completed before 5 seconds.

0
votes

I think there's a possible loss of task items between

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

and

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

because some tasks may ran to completition during this time.

As a workaround (not correct though) you coud swap these lines. In this case some tasks may present in each (completedTasks and incompleteTasks) list. But maybe it's better than to be lost completely.

A unit test to compare number of started tasks and number of tasks in completedTasks and incompleteTasks lists may also be useful.