Task.WhenAll(IEnumerable<Task>)
等待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");
}
输出:
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
第二个Task.WhenAll()
在这种情况下解决了问题,但这是一个相当脆弱的解决方案。在一般情况下处理这个问题的最佳方法是什么?
答案 0 :(得分:2)
您正在修改List<>
而不锁定它...您希望过着危险的生活:-)在执行Count
之前保存_tasks
的{{1}} ,然后在WaitAll
检查WaitAll
的{{1}}之后。如果不同,请进行另一轮(因此Count
周围需要_tasks
。
while
我会添加第二个(可能更正确的解决方案),这与您正在做的不同:您只需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");
}
s的新await
s生成它们,而不将它们级联到Task
集合。如果A创建B,则A在B完成之前不会完成。显然,您无需将新Task
添加到_tasks
集合。
答案 1 :(得分:1)
异步函数将在第一个await
返回给调用者
因此,在将额外任务添加到原始任务列表之前,for
循环将完成。
Task.WhenAll
的实现会将任务迭代/复制到本地列表,因此在调用Task.WhenAll
之后添加的任务将被忽略。
在您的特定情况下,在Func1
之前将呼叫转移到await Task.Delay()
可能是一个解决方案。
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");
}
但是如果在实际场景中调用Func2
取决于某种异步方法的结果,那么你需要一些其他解决方案。
答案 2 :(得分:0)
考虑一下;听起来工作正在提交给&#34;任务列表&#34;从另一个线程。从某种意义上说,&#34;任务提交&#34;线程本身也也是另一个让你等待的任务。
如果您等待提交所有任务,那么保证您下次调用WhenAll
将产生完整的有效负载。
您的等待功能可能/应该分为两个步骤:
示例:
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!
}
答案 3 :(得分:0)
由于在执行原始任务列表的过程中似乎可以创建其他任务,因此您需要一个简单的while
构造。
while (_tasks.Any( t => !t.IsCompleted )
{
await Task.WhenAll(_tasks);
}
这将检查列表中是否有任何未完成的任务并等待它们,直到它在没有任务的情况下捕获列表。