Task.WhenAll(IEnumerable<Task>)
waits for all tasks in the IEnumerable are complete --- but only the tasks in the list when it's first called. If any active task adds to the list, they aren't considered. This short example demonstrates:
Task.WhenAll(IEnumerable
List<Task> _tasks = new List<Task>();
public async Task QuickExample()
{
for(int n =0; n < 6; ++n)
_tasks.Add(Func1(n));
await Task.WhenAll(_tasks);
Console.WriteLine("Some Tasks complete");
await Task.WhenAll(_tasks);
Console.WriteLine("All Tasks complete");
}
async Task Func1(int n)
{
Console.WriteLine($"Func1-{n} started");
await Task.Delay(2000);
if ((n % 3) == 1)
_tasks.Add(Func2(n));
Console.WriteLine($"Func1-{n} complete");
}
async Task Func2(int n)
{
Console.WriteLine($"Func2-{n} started");
await Task.Delay(2000);
Console.WriteLine($"Func2-{n} complete");
}
This outputs:
这输出:
Func1-0 started
Func1-1 started
Func1-2 started
Func1-3 started
Func1-4 started
Func1-5 started
Func1-5 complete
Func1-3 complete
Func2-1 started
Func1-1 complete
Func1-0 complete
Func1-2 complete
Func2-4 started
Func1-4 complete
Some Tasks complete
Func2-4 complete
Func2-1 complete
All Tasks complete
Done
The second Task.WhenAll()
solves the problem in this case, but that's a rather fragile solution. What's the best way to handle this in the general case?
第二个Task.WhenAll()在这种情况下解决了问题,但这是一个相当脆弱的解决方案。在一般情况下,处理此问题的最佳方法是什么?
4 个解决方案
#1
2
You are modifying the List<>
without locking it... You like to live a dangerous life :-) Save the Count
of the _tasks
before doing a WaitAll
, then after the WaitAll
check the Count
of _tasks
. If it is different, do another round (so you need a while
around the WaitAll
.
你正在修改List <>而没有锁定它......你喜欢过着危险的生活:-)在执行WaitAll之前保存_tasks的Count,然后在WaitAll之后检查_tasks的Count。如果不同,请再做一轮(所以你需要在WaitAll周围休息一下。
int count = _tasks.Count;
while (true)
{
await Task.WhenAll(_tasks);
lock (_tasks)
{
if (count == _tasks.Count)
{
Console.WriteLine("All Tasks complete");
break;
}
count = _tasks.Count;
Console.WriteLine("Some Tasks complete");
}
}
async Task Func1(int n)
{
Console.WriteLine($"Func1-{n} started");
await Task.Delay(2000);
if ((n % 3) == 1)
{
lock (_tasks)
{
_tasks.Add(Func2(n));
}
}
Console.WriteLine($"Func1-{n} complete");
}
I'll add a second (probably more correct solution), that is different from what you are doing: you could simply await
the new Task
s from the Task
s that generated them, without cascading them to the _tasks
collection. If A creates B, then A doesn't finish until B finishes. Clearly you don't need to add the new Task
s to the _tasks
collection.
我将添加第二个(可能更正确的解决方案),这与您正在执行的操作不同:您只需等待生成它们的任务中的新任务,而无需将它们级联到_tasks集合。如果A创建B,则A在B完成之前不会完成。显然,您不需要将新任务添加到_tasks集合中。
#2
1
Asynchronous function will return to the caller on first await
.
So for
loop will be complete before you add extra tasks to original tasks list.
异步函数将在第一次等待时返回给调用者。因此,在将额外任务添加到原始任务列表之前,将完成循环。
Implementation of Task.WhenAll
will iterate/copy tasks to local list, so added tasks after Task.WhenAll
called will be ignored.
Task.WhenAll的实现将迭代/复制任务到本地列表,因此在调用Task.WhenAll之后添加的任务将被忽略。
In your particular case moving call to Func1
before await Task.Delay()
could be a solution.
在您的特定情况下,在等待Task.Delay()之前将调用移动到Func1可能是一个解决方案。
async Task Func1(int n)
{
Console.WriteLine($"Func1-{n} started");
if ((n % 3) == 1)
_tasks.Add(Func2(n));
await Task.Delay(2000);
Console.WriteLine($"Func1-{n} complete");
}
But if in real scenario calling of Func2
depend on result of some asynchronous method, then you need some other solution.
但是如果在实际情况下调用Func2取决于某些异步方法的结果,那么你需要一些其他的解决方案。
#3
0
Consider this; it sounds like work is being submitted to the "Task List" from another thread. In a sense, the "task submission" thread itself could also be yet another Task for you to wait on.
考虑一下;听起来工作正在从另一个线程提交到“任务列表”。从某种意义上说,“任务提交”线程本身也可能是你要等待的另一个任务。
If you wait for all Tasks to be submitted, then you are guaranteed that your next call to WhenAll
will yield a fully-completed payload.
如果您等待提交所有任务,那么您可以保证下次调用WhenAll将产生完全完成的有效负载。
Your waiting function could/should be a two-step process:
您的等待功能可能/应该是一个两步过程:
- Wait for the "Task Submitting" task to complete, signalling all Tasks are submitted
- 等待“任务提交”任务完成,表明所有任务都已提交
- Wait for all the submitted tasks to complete.
- 等待所有提交的任务完成。
Example:
例:
public async Task WaitForAllSubmittedTasks()
{
// Work is being submitted in a background thread;
// Wrap that thread in a Task, and wait for it to complete.
var workScheduler = GetWorkScheduler();
await workScheduler;
// All tasks submitted!
// Now we get the completed list of all submitted tasks.
// It's important to note: the submitted tasks
// have been chugging along all this time.
// By the time we get here, there are probably a number of
// completed tasks already. It does not delay the speed
// or execution of our work items if we grab the List
// after some of the work has been completed.
//
// It's entirely possible that - by the time we call
// this function and wait on it - almost all the
// tasks have already been completed!
var submittedWork = GetAllSubmittedTasks();
await Task.WhenAll(submittedWork);
// Work complete!
}
#4
0
Since it seems that additional tasks can be created during the course of executing the original list of tasks, you will need a simple while
construct.
由于在执行原始任务列表的过程中似乎可以创建其他任务,因此您需要一个简单的while结构。
while (_tasks.Any( t => !t.IsCompleted )
{
await Task.WhenAll(_tasks);
}
This will check the list for any uncompleted tasks and await them until it catches the list at a moment when there are no tasks left.
这将检查列表中是否有任何未完成的任务并等待它们,直到它在没有任务的情况下捕获列表。
#1
2
You are modifying the List<>
without locking it... You like to live a dangerous life :-) Save the Count
of the _tasks
before doing a WaitAll
, then after the WaitAll
check the Count
of _tasks
. If it is different, do another round (so you need a while
around the WaitAll
.
你正在修改List <>而没有锁定它......你喜欢过着危险的生活:-)在执行WaitAll之前保存_tasks的Count,然后在WaitAll之后检查_tasks的Count。如果不同,请再做一轮(所以你需要在WaitAll周围休息一下。
int count = _tasks.Count;
while (true)
{
await Task.WhenAll(_tasks);
lock (_tasks)
{
if (count == _tasks.Count)
{
Console.WriteLine("All Tasks complete");
break;
}
count = _tasks.Count;
Console.WriteLine("Some Tasks complete");
}
}
async Task Func1(int n)
{
Console.WriteLine($"Func1-{n} started");
await Task.Delay(2000);
if ((n % 3) == 1)
{
lock (_tasks)
{
_tasks.Add(Func2(n));
}
}
Console.WriteLine($"Func1-{n} complete");
}
I'll add a second (probably more correct solution), that is different from what you are doing: you could simply await
the new Task
s from the Task
s that generated them, without cascading them to the _tasks
collection. If A creates B, then A doesn't finish until B finishes. Clearly you don't need to add the new Task
s to the _tasks
collection.
我将添加第二个(可能更正确的解决方案),这与您正在执行的操作不同:您只需等待生成它们的任务中的新任务,而无需将它们级联到_tasks集合。如果A创建B,则A在B完成之前不会完成。显然,您不需要将新任务添加到_tasks集合中。
#2
1
Asynchronous function will return to the caller on first await
.
So for
loop will be complete before you add extra tasks to original tasks list.
异步函数将在第一次等待时返回给调用者。因此,在将额外任务添加到原始任务列表之前,将完成循环。
Implementation of Task.WhenAll
will iterate/copy tasks to local list, so added tasks after Task.WhenAll
called will be ignored.
Task.WhenAll的实现将迭代/复制任务到本地列表,因此在调用Task.WhenAll之后添加的任务将被忽略。
In your particular case moving call to Func1
before await Task.Delay()
could be a solution.
在您的特定情况下,在等待Task.Delay()之前将调用移动到Func1可能是一个解决方案。
async Task Func1(int n)
{
Console.WriteLine($"Func1-{n} started");
if ((n % 3) == 1)
_tasks.Add(Func2(n));
await Task.Delay(2000);
Console.WriteLine($"Func1-{n} complete");
}
But if in real scenario calling of Func2
depend on result of some asynchronous method, then you need some other solution.
但是如果在实际情况下调用Func2取决于某些异步方法的结果,那么你需要一些其他的解决方案。
#3
0
Consider this; it sounds like work is being submitted to the "Task List" from another thread. In a sense, the "task submission" thread itself could also be yet another Task for you to wait on.
考虑一下;听起来工作正在从另一个线程提交到“任务列表”。从某种意义上说,“任务提交”线程本身也可能是你要等待的另一个任务。
If you wait for all Tasks to be submitted, then you are guaranteed that your next call to WhenAll
will yield a fully-completed payload.
如果您等待提交所有任务,那么您可以保证下次调用WhenAll将产生完全完成的有效负载。
Your waiting function could/should be a two-step process:
您的等待功能可能/应该是一个两步过程:
- Wait for the "Task Submitting" task to complete, signalling all Tasks are submitted
- 等待“任务提交”任务完成,表明所有任务都已提交
- Wait for all the submitted tasks to complete.
- 等待所有提交的任务完成。
Example:
例:
public async Task WaitForAllSubmittedTasks()
{
// Work is being submitted in a background thread;
// Wrap that thread in a Task, and wait for it to complete.
var workScheduler = GetWorkScheduler();
await workScheduler;
// All tasks submitted!
// Now we get the completed list of all submitted tasks.
// It's important to note: the submitted tasks
// have been chugging along all this time.
// By the time we get here, there are probably a number of
// completed tasks already. It does not delay the speed
// or execution of our work items if we grab the List
// after some of the work has been completed.
//
// It's entirely possible that - by the time we call
// this function and wait on it - almost all the
// tasks have already been completed!
var submittedWork = GetAllSubmittedTasks();
await Task.WhenAll(submittedWork);
// Work complete!
}
#4
0
Since it seems that additional tasks can be created during the course of executing the original list of tasks, you will need a simple while
construct.
由于在执行原始任务列表的过程中似乎可以创建其他任务,因此您需要一个简单的while结构。
while (_tasks.Any( t => !t.IsCompleted )
{
await Task.WhenAll(_tasks);
}
This will check the list for any uncompleted tasks and await them until it catches the list at a moment when there are no tasks left.
这将检查列表中是否有任何未完成的任务并等待它们,直到它在没有任务的情况下捕获列表。