0

I have created the following in order to execute multiple async tasks with a timeout. I was looking for something that will allow extracting results from the tasks - taking only those that beat the timeout, regardless if the rest of tasks failed to do so (simplified):

TimeSpan timeout = TimeSpan.FromSeconds(5.0);

Task<Task>[] tasksOfTasks =
{
    Task.WhenAny(SomeTaskAsync("a"), Task.Delay(timeout)),
    Task.WhenAny(SomeTaskAsync("b"), Task.Delay(timeout)),
    Task.WhenAny(SomeTaskAsync("c"), Task.Delay(timeout))
};

Task[] completedTasks = await Task.WhenAll(tasksOfTasks);

List<MyResult> = completedTasks.OfType<Task<MyResult>>().Select(task => task.Result).ToList();

I have implemented this in a non-static class in the (Web API) server.

This worked well on the first call, however additional calls caused completedTasks to strangely accumulate tasks from previous calls to the server (as shown by the debugger). On the second call there were 6 completed tasks, on the third call 9 and so on.

My questions:

  1. Any idea why is that? I assume it's because the previous tasks weren't cancelled however this code is in a new instance of a class!
  2. Any idea how to avoid this accumulation?

PS: See my answer to this question.

Community
  • 1
  • 1
Erez Cohen
  • 1,507
  • 2
  • 16
  • 25
  • I have edited the other question - http://stackoverflow.com/questions/25716156/how-to-multiple-async-tasks-with-timeout-and-cancellation. Please read both carefully and you'll find that these are two different questions (+ with different code segments). – Erez Cohen Sep 08 '14 at 14:33

1 Answers1

3

I couldn't use my psychic debugging to understand why your code "caused completedTasks to strangely accumulate tasks from previous calls" but it does probably expose some of your misunderstandings.

Here's a working example based on your code (using string instead of MyResult):

Task<string> timeoutTask = 
    Task.Delay(TimeSpan.FromSeconds(5)).ContinueWith(_ => string.Empty);

Task<Task<string>>[] tasksOfTasks =
{
    Task.WhenAny(SomeTaskAsync("a"), timeoutTask),
    Task.WhenAny(SomeTaskAsync("b"), timeoutTask),
    Task.WhenAny(SomeTaskAsync("c"), timeoutTask)
};
Task<string>[] completedTasks = await Task.WhenAll(tasksOfTasks);

List<string> results = completedTasks.Where(task => task != timeoutTask).
    Select(task => task.Result).ToList();

So, what's different:

  • I'm using the same timeout task for all WhenAny calls. There's no need to use more, and they could complete in slightly different times.
  • I make the timeout task return a value, so it's actually a Task<string> and not a Task.
  • That makes each WhenAny call also return a Task<string> (and tasksOfTasks be Task<Task<string>>[]) which would make it possible to actually return a result out of these tasks.
  • After awaiting we need to filter out WhenAny calls that returned our timeout task, because there would be no result there (only string.Empty) using completedTasks.Where(task => task != timeoutTask).

P.S : I've also answered that question and I would (surprisingly) recommend you use my solution.


Note: Using the Task.Result property isn't advisable. You should await it instead (even when you know it's already completed)

Community
  • 1
  • 1
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • Thanks. I ran many tests and realized that when I initialize `tasksOfTasks` using an initializer as above, everything went well, but when I create the collection dynamically (e.g. by using a LINQ query into an `IEnumerable>`) - which I need to do, tasks started to accumulate again. The sad thing is, when I tried your example above initialized dynamically, it still went fine, which means it might be other complexity in my code (which of course is more complex than what I've shown here). – Erez Cohen Sep 09 '14 at 16:37