2017-08-25 46 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