2017-08-25 31 views
0

我只是偶然发现的Task.WhenAll重载之一,即需要一个IEnumerable作为参数Task.WhenAll(IEnumerable):任务启动两次?

public static Task WhenAll(IEnumerable<Task<TResult>> tasks) 

我想我会尝试用下面的短节目此功能。

在测试类:

// contains the task numbers that has been run 
private HashSet<int> completedTasks = new HashSet<int>(); 

// async function. waits a while and marks that it has been run: 
async Task<int> Calculate(int taskNr) 
{ 
    string msg = completedTasks.Contains(taskNr) ? 
     "This task has been run before" : 
     "This is the first time this task runs"; 
    Console.WriteLine($"Start task {i} {msg}"); 

    await Task.Delay(TimeSpan.FromMilliseconds(100)); 

    Console.WriteLine($"Finished task {taskNr}"); 
    // mark that this task has been run: 
    completedTasks.Add(taskNr); 
    return i; 
} 

// async test function that uses Task.WhenAll(IEnumerable) 
public async Task TestAsync() 
{ 
    Console.Write("Create the task enumerators... "); 
    IEnumerable<Task<int>> tasks = Enumerable.Range(1, 3) 
     .Select(i => Calculate(i)); 
    Console.WriteLine("Done!"); 

    Console.WriteLine("Start Tasks and await"); 
    await Task.WhenAll(tasks); 
    Console.WriteLine("Finished waiting. Results:"); 

    foreach (var task in tasks) 
    { 
     Console.WriteLine(task.Result); 
    } 
} 

最后主程序:

static void Main(string[] args) 
{ 
    var testClass = new TestClass(); 
    Task t = Task.Run(() => testClass.TestAsync()); 
    t.Wait(); 
} 

输出如下:

Create the task enumerators... Done! 
Start Tasks and wait 
Start task 1 This is the first time this task runs 
Start task 2 This is the first time this task runs 
Start task 3 This is the first time this task runs 
Finished task 2 
Finished task 3 
Finished task 1 
Finished waiting. Results: 
Start task 1 This task has been run before 
Finished task 1 
1 
Start task 2 This task has been run before 
Finished task 2 
2 
Start task 3 This task has been run before 
Finished task 3 
3 

显然,每个任务运行两次!我在做什么错?

更奇怪的是:如果我在Task.Whenall之前使用ToList()来列举任务序列,则该函数按预期工作!

+0

“请注意,此函数不是异步的,它不是可等待的” - 它*是*可等待的。一个函数不需要被标记为“async”是可以等待的 - 它只需要返回遵循awaitable-awaiter模式的东西。 “Task”是该模式的海报孩子。 –

+0

Sprry,那是我的第一个版本。后来我意识到这可能是问题,我把所有事情都改变为异步等待。更正了问题 –

回答

8

您的问题是推迟执行。这条线

IEnumerable<Task<int>> tasks = Enumerable.Range(1, 3) 
    .Select(i => Calculate(i)); 

更改为

var tasks = Enumerable.Range(1, 3) 
    .Select(i => Calculate(i)).ToList(); 

Select()执行 “查询” 立刻,而是返回一个枚举。只有当你使用这个枚举器迭代任务时,内部lambda被调用序列1 ... 3。
在您的版本中,每次迭代通过tasks时,Calculate(i)被再次调用并创建新任务。
使用.ToList()枚举器被执行一次,并且结果的Task<int>的序列被存储在List<Task<int>>中(并且当该列表被第二次枚举时不会再次生成)。


当你调用Task.WhenAll(tasks)这种方法遍历tasks,由此开始每项任务。当您稍后再次迭代时(使用foreach循环输出结果),将再次执行,从而启动新任务。

+0

事实上,您的解决方案正如我在问题结尾处所写的那样有效。但是,使用延迟执行时,我希望它在Task.Whenall枚举序列时执行,显然Task.WhenAll枚举两次。 –

+3

'Task.WhenAll'执行一次,然后在'for'循环中再次执行。 – DavidG

+0

“那么你再做一次” - 恩,枚举器再次做它...... :) – Fildor