Reputation: 983
I'm pretty new to async programming. I want to kick off a series of tasks (which make http requests) before awaiting any of them.
List<Guid> identifiers;
//Set identifiers to what they should be
var task = Task.WhenAll(identifiers.Select(id => _serviceConnector.GetAsync(id)));
// Call and await another request
await task;
My question is: Will my http requests be kicked off with the createion of the task through Task.WhenAll? Or will they not be started until the await further down? Thanks!
Upvotes: 2
Views: 2258
Reputation: 236278
Will my http requests be kicked off with the creation of the task through Task.WhenAll? Or will they not be started until the await further down?
When you pass IEnumerable<Task>
to Task.WhenAll
it enumerates the sequence of tasks and puts them all to list for further processing:
if (tasks == null) throw new ArgumentNullException("tasks");
List<Task<TResult>> taskList = new List<Task<TResult>>();
foreach (Task<TResult> task in tasks)
{
if (task == null) throw new ArgumentException("tasks");
taskList.Add(task);
}
that means if you pass a LINQ query, it will be executed:
identifiers.Select(id => _serviceConnector.GetAsync(id))
but it does not mean that Task.WhenAll
starts those tasks. E.g. if your query will return tasks which have not been started, then those tasks will stay in non-running state. E.g. following query will create tasks but will not start them, thus WhenAll
will stuck waiting for these tasks
var tasks = Enumerable.Range(1, 10).Select(i => new Task<int>(() => i));
var task = Task.WhenAll(tasks);
In your case everything depends on GetAsync(id)
method. If it creates and starts a task (like HttpClient
does) then all tasks would be created and started at the beginning of Task.WhenAll
call.
TL;DR Implementation details of Task.WhenAll
method. As stated above, it grabs all given tasks (for IEnumerable argument it puts all tasks into list) and creates a new task of type WhenAllPromise<T>
private sealed class WhenAllPromise<T> : Task<T[]>, ITaskCompletionAction
As you can see this task implements ITaskCompletionAction
. This is an internal interface, which is used to add completion action to tasks - the lightweight version of continuation tasks (because it's a simple action). This interface defines single method Invoke(Task)
which should be invoked when task completes execution. Task
has an internal method which allows adding these lightweight continuations:
internal void AddCompletionAction(ITaskCompletionAction action)
Now back to WhenAllPromise<T>
class. It has two fields:
private readonly Task<T>[] m_tasks;
private int m_count;
During initialization this class stores all given tasks in field array, initializes counter, and either invokes continuation for already completed tasks or adds itself to task completion actions:
m_tasks = tasks;
m_count = tasks.Length;
foreach (var task in tasks)
{
if (task.IsCompleted) this.Invoke(task); // short-circuit
else task.AddCompletionAction(this); // simple completion action
}
That's it. Tasks are not started by WhenAllPromise
class. It only uses callback action which is invoked when tasks are completed. In callback action it decreases m_count each time some of tasks is completed, until all tasks are done and we can grab results.
Upvotes: 3
Reputation: 456977
WhenAll
immediately (and synchronously) reifies its enumerable argument. So all tasks would be started by the time WhenAll
returns.
If you think about this, it makes sense. WhenAll
must know how many tasks it is waiting for so it knows when its own task is complete. Also, it must link to each of those tasks so it's notified as each child task completes. There's no other time to do this work; it must count and set up notifications before it returns.
Upvotes: 3