3

I would like to fire several tasks while setting a timeout on them. The idea is to gather the results from the tasks that beat the clock, and cancel (or even just ignore) the other tasks.

I tried using extension methods WithCancellation as explained here, however throwing an exception caused WhenAll to return and supply no results.

Here's what I tried, but I'm opened to other directions as well (note however that I need to use await rather than Task.Run since I need the httpContext in the Tasks):

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));

IEnumerable<Task<MyResults>> tasks = 
            from url in urls
            select taskAsync(url).WithCancellation(cts.Token);

Task<MyResults>[] excutedTasks = null;

MyResults[] res = null;
try
{
    // Execute the query and start the searches:
    excutedTasks = tasks.ToArray();

    res = await Task.WhenAll(excutedTasks);
}
catch (Exception exc)
{
    if (excutedTasks != null)
    {
        foreach (Task<MyResults> faulted in excutedTasks.Where(t => t.IsFaulted))
        {
            // work with faulted and faulted.Exception
        }
    }
}

// work with res

EDIT: Following @Servy's answer below, this is the implementation I went with:

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));

IEnumerable<Task<MyResults>> tasks = 
            from url in urls
            select taskAsync(url).WithCancellation(cts.Token);

// Execute the query and start the searches:
Task<MyResults>[] excutedTasks = tasks.ToArray();

try
{
    await Task.WhenAll(excutedTasks);
}
    catch (OperationCanceledException)
{
    // Do nothing - we expect this if a timeout has occurred
}

IEnumerable<Task<MyResults>> completedTasks = excutedTasks.Where(t => t.Status == TaskStatus.RanToCompletion);

var results = new List<MyResults>();
completedTasks.ForEach(async t => results.Add(await t));
Community
  • 1
  • 1
Erez Cohen
  • 1,507
  • 2
  • 16
  • 25

2 Answers2

1

If any of the tasks fail to complete you are correct that WhenAll doesn't return the results of any that did complete, it just wraps an aggregate exception of all of the failures. Fortunately, you have the original collection of tasks, so you can get the results that completed successfully from there.

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

Just use that instead of res.

Servy
  • 202,030
  • 26
  • 332
  • 449
0

I tried you code and it worked just fine, except the cancelled tasks are in not in a Faulted state, but rather in the Cancelled. So if you want to process the cancelled tasks use t.IsCanceled instead. The non cancelled tasks ran to completion. Here is the code I used:

    public static async Task MainAsync()
    {
        var urls = new List<string> {"url1", "url2", "url3", "url4", "url5", "url6"};

        var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));

        IEnumerable<Task<MyResults>> tasks =
            from url in urls
            select taskAsync(url).WithCancellation(cts.Token);

        Task<MyResults>[] excutedTasks = null;

        MyResults[] res = null;
        try
        {
            // Execute the query and start the searches:
            excutedTasks = tasks.ToArray();

            res = await Task.WhenAll(excutedTasks);
        }
        catch (Exception exc)
        {
            if (excutedTasks != null)
            {
                foreach (Task<MyResults> faulted in excutedTasks.Where(t => t.IsFaulted))
                {
                    // work with faulted and faulted.Exception
                }
            }
        }

    }

    public static async Task<MyResults> taskAsync(string url)
    {
        Console.WriteLine("Start " + url);
        var random = new Random();
        var delay = random.Next(10);
        await Task.Delay(TimeSpan.FromSeconds(delay));

        Console.WriteLine("End " + url);

        return new MyResults();
    }

    private static void Main(string[] args)
    {
        MainAsync().Wait();
    }
NeddySpaghetti
  • 13,187
  • 5
  • 32
  • 61
  • Thanks for running it! It is true that tasks run to completion, however once a cancellation occurred, WhenAll returns and in `res` I get no results. I was expecting to find there the results of the tasks that were completed before the time out. I added some code (between the `//////////////` to show this). – Erez Cohen Sep 08 '14 at 14:16
  • (I edited my original question to emphasize this 'Results' part). – Erez Cohen Sep 08 '14 at 14:22