2017-08-25 5 views
0

私はちょうどTask.WhenAllのオーバーロードの1、私は、次の短いプログラムでこの機能を試してみようと思いましたパラメータTask.WhenAll(IEnumerable):タスクは2回開始されますか?

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

などのIEnumerableを取る1つまずきました。 Testクラスで

// 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'としてマークする必要はありません - 待ち時間のある待ち行列のパターンに続くものを返さなければなりません。 'Task'はパターンの* poster子です。 –

+0

Sprry、それは私の最初のバージョンでした。後でそれが問題になることがわかったので、すべてを非同期に変更しました。質問を修正しました –

答えて

8

問題は延期です。

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

Select()

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

にこの行を変更することは はすぐ "クエリ" を実行しますが、列挙子を返していません。この列挙子を使用してタスクを反復する場合にのみ、シーケンス1 ... 3の内部ラムダが呼び出されます。
ご使用のバージョンでは、 tasksを反復するたびに、 Calculate(i)が再度呼び出され、新しいタスクが作成されます。
.ToList()とすると、列挙子は1回実行され、 Task<int>の結果のシーケンスは List<Task<int>>に格納されます(2回目の列挙時に再び生成されません)。


あなたはtasksを通じて、この方法が繰り返さTask.WhenAll(tasks)を呼び出すことにより、各タスクを開始します。後で再度反復すると(foreachループで結果が出力されます)、クエリはからに実行され、新しいタスクが開始されます。

+0

実際にあなたの解決策は、私が質問の最後に書いたように動作します。しかし、遅延実行を使用すると、Task.Whenallがシーケンスを列挙したときに実行されると予想されました。明らかにTask.WhenAllは2回列挙されます。 –

+3

'Task.WhenAll'はそれを一度行い、' for'ループでもう一度やります。 – DavidG

+0

"それからもう一度やります" - 列挙子はもう一度やります... :) – Fildor

関連する問題