2016-03-22 7 views
6

現在、進行中の不確定な形で、行の最後に連続してドットを印刷しようとしています。 tasksList<Task> tasks = new List<Task>();
tasks.Add(Task.Run(() => someMethodAsync()));からであるリスト内のすべてのタスクが完了するまで進行状況を表示<Task>

start = DateTime.Now; 
Console.Write("*Processing variables"); 
Task entireTask = Task.WhenAll(tasks); 
Task progress = new Task(() => { while (!entireTask.IsCompleted) { Console.Write("."); System.Threading.Thread.Sleep(1000); } }); 
progress.Start(); 
entireTask.Wait(); 
timeDiff = DateTime.Now - start; 
Console.WriteLine("\n*Operation completed in {0} seconds.", timeDiff.TotalSeconds); 

は倍の10000さんが発生しました。
このコードは現在動作していますが、これを達成する正しい方法ですが、これが最も費用対効果の高い方法ですか?

答えて

6

これは確かに解決できる方法がいくつかあり、そのうちの1つはあなたのものです。しかし、特に同期待機(Thread.Sleep)以外の何もしない場合、長時間実行するタスクを開始することは、実際には良い習慣ではありません。

テクニカルとドメインの部分でコードをリファクタリングすることを検討する必要があります。技術的な部分がある:

  1. まで待つことに時間がかかる場合は、指定されたコレクション内のすべてのタスクが
  2. を完了するまで定期的に進捗状況は次のコードは、このビットよりよく理解するために役立つかもしれない

を報告します。これは、異なる非同期操作をシミュレートし、それらがすべて完了するのを待つ4つのタスクを開始します。これが250msより長くかかる場合、WhenAllExの呼び出しは、進捗報告を再発行するためにラムダを呼び出すことを継続する。

static void Main(string[] args) 
{ 
    var tasks = Enumerable.Range(0, 4).Select(taskNumber => Task.Run(async() => 
    { 
     Console.WriteLine("Task {0} starting", taskNumber); 
     await Task.Delay((taskNumber + 1) * 1000); 
     Console.WriteLine("Task {0} stopping", taskNumber); 
    })).ToList(); 

    // Wait for all tasks to complete and do progress report 
    var whenAll = WhenAllEx(
     tasks, 
     _ => Console.WriteLine("Still in progress. ({0}/{1} completed)", _.Count(task => task.IsCompleted), tasks.Count())); 

    // Usually never wait for asynchronous operations unless your in Main 
    whenAll.Wait(); 
    Console.WriteLine("All tasks finished"); 
    Console.ReadKey(); 
} 

/// <summary> 
/// Takes a collection of tasks and completes the returned task when all tasks have completed. If completion 
/// takes a while a progress lambda is called where all tasks can be observed for their status. 
/// </summary> 
/// <param name="tasks"></param> 
/// <param name="reportProgressAction"></param> 
/// <returns></returns> 
public static async Task WhenAllEx(ICollection<Task> tasks, Action<ICollection<Task>> reportProgressAction) 
{ 
    // get Task which completes when all 'tasks' have completed 
    var whenAllTask = Task.WhenAll(tasks); 
    for (; ;) 
    { 
     // get Task which completes after 250ms 
     var timer = Task.Delay(250); // you might want to make this configurable 
     // Wait until either all tasks have completed OR 250ms passed 
     await Task.WhenAny(whenAllTask, timer); 
     // if all tasks have completed, complete the returned task 
     if (whenAllTask.IsCompleted) 
     { 
      return; 
     } 
     // Otherwise call progress report lambda and do another round 
     reportProgressAction(tasks); 
    } 
} 
+0

この行は "var whenAllTask​​ = Task.WhenAll(tasks);"すべてのタスクが完了するまでブロックされますか?コードは常にすべてのタスクが完了するのを待ってからTask.Delayを実行しますか? –

+0

Task.WhenAll(...)はブロックしませんが、パラメータ内のすべてのタスクが完了すると完了するタスクを返します。 Task.Delayも同様です。 250ms後に完了するタスクを返します(サンプルコード内)。トリックは、Task.WhenAllまたはTask.Delayが完了したときに完了する3番目のタスクを待つことです。私はコードにいくつかのコメントを追加します。 –

+0

お返事ありがとうございました!私は2つの構文の質問があります。 'var whenAll'のためのパラメータで使ったアンダースコアで構文上何が起きているのですか?' for(;;) 'の構文で何が起こっていますか? – cloudcrypt

3

トーマスの答えは良いです、あなたはそれを受け入れるべきです。私は、小さなコードデルタとバージョンを提供します:

はこれを置き換えます

Task progressTask = Task.Run(async() => { 
while (!entireTask.IsCompleted) { 
    Console.Write("."); 
    await Task.Delay(1000); 
} 
}); 

これは、より効率的であると、私は考えて、クリーンなコード:これにより

Task progress = new Task(() => { while (!entireTask.IsCompleted) { Console.Write("."); System.Threading.Thread.Sleep(1000); } }); 
progress.Start(); 

を。

「トータルタスク」が完了したらすぐに進行タスクを終了するという追加の利点がありました。

+1

'await Task.Delay(1000)'と 'System.Threading.Thread.Sleep(1000)'のメリットと違いは何ですか? – cloudcrypt

+0

また、構文上、 'Task.Run'のパラメータの' async'の意味は何ですか? – cloudcrypt

+0

ブロックしません。これにより、それ以外の場合は常に待機しているスレッドが節約されます。 – usr

9

トーマスが述べたように、これを処理する方法は確かにいくつかあります。私のために心にすぐにスプリング1は次のようになります。このアプローチは、このようにasyncになるこの方法を必要とし、awaitを使用しないこと

start = DateTime.Now; 
Console.Write("*Processing variables"); 
Task entireTask = Task.WhenAll(tasks); 
while (await Task.WhenAny(entireTask, Task.Delay(1000)) != entireTask) 
{ 
    Console.Write("."); 
} 
timeDiff = DateTime.Now - start; 
Console.WriteLine("\n*Operation completed in {0} seconds.", timeDiff.TotalSeconds); 

注意。通常、コンソールアプリケーションの場合、を呼び出すことをお勧めします。ブロッキング(またはメインループ)はすべて1行のコードであり、ロジックと混ざらないようにしてください。

+0

これを実装して、タスクの実行全体の実際の進捗状況をパーセンテージで報告できますか?そのためのアプローチは? 残念ながら 'List >のようなパラメータを' WhenAny'に設定することはできません。ここでは、 'completeTask'という大きなタスクが1つだけあります。完了状態を待つ必要があります。 – Legends

+1

@Legends:Progressレポートは個別に(現在のように)作成することも、累積することもできます(「現在のものをDoProgressに移動」)。個々の進捗報告は本質的に「別のものが完了した」と言っています。累積報告書は、「これらの多くは完了しました」と言います。 –

関連する問題