2017-05-26 10 views
2

私は10タスクのリストを持っています、各タスクは15秒かかります。すべてのタスクは配列内にあり、非同期に実行されます。セット全体に約15秒かかりますか?下のコードから、出力ではセット全体が21秒かかることに注意してください。私はそれを100のタスクに変更すると、1分以上かかる。ほとんどの場合、タスクを作成するのに1秒かかります。私は何が欠けていますか?ありがとう!タスクの配列を非同期に実行するとき、実行時間が最も長いタスクを使用しないでください。

static void Main(string[] args) 
{ 
    var mainStart = DateTime.Now; 
    var tasks = new List<Task>(); 
    for (int i = 0; i < 10; i++) 
    { 
     tasks.Add(Task.Factory.StartNew((Object data) => 
     { 
      var index = (int)data; 
      var stepStart = DateTime.Now; 
      Console.WriteLine("{0} Starting {1} on thread {2}...", stepStart, index, Thread.CurrentThread.ManagedThreadId); 
      Thread.Sleep(15000); 
      var stepFinish = DateTime.Now; 
      Console.WriteLine("{0} Finished {1} on thread {2}, duration: {3}", 
       stepStart, index, Thread.CurrentThread.ManagedThreadId, stepFinish - stepStart); 
     }, 
     i)); 
    } 
    Task.WaitAll(tasks.ToArray()); 
    var mainFinish = DateTime.Now; 
    Console.WriteLine("{0} Finished, duration {1}", DateTime.Now, mainFinish - mainStart); 
    Console.WriteLine("Press any key to exit."); 
    Console.Read(); 

    // Output 
    //5/25/2017 8:03:43 PM Starting 0 on thread 10... 
    //5/25/2017 8:03:43 PM Starting 1 on thread 11... 
    //5/25/2017 8:03:43 PM Starting 2 on thread 12... 
    //5/25/2017 8:03:43 PM Starting 3 on thread 13... 
    //5/25/2017 8:03:44 PM Starting 4 on thread 14... 
    //5/25/2017 8:03:45 PM Starting 5 on thread 15... 
    //5/25/2017 8:03:46 PM Starting 6 on thread 16... 
    //5/25/2017 8:03:47 PM Starting 7 on thread 17... 
    //5/25/2017 8:03:48 PM Starting 8 on thread 18... 
    //5/25/2017 8:03:49 PM Starting 9 on thread 19... 
    //5/25/2017 8:03:43 PM Finished 0 on thread 10, duration: 00:00:15.0018957 
    //5/25/2017 8:03:43 PM Finished 1 on thread 11, duration: 00:00:15.0175209 
    //5/25/2017 8:03:43 PM Finished 2 on thread 12, duration: 00:00:15.0175209 
    //5/25/2017 8:03:43 PM Finished 3 on thread 13, duration: 00:00:15.0165291 
    //5/25/2017 8:03:44 PM Finished 4 on thread 14, duration: 00:00:15.0156567 
    //5/25/2017 8:03:45 PM Finished 5 on thread 15, duration: 00:00:15.0156012 
    //5/25/2017 8:03:46 PM Finished 6 on thread 16, duration: 00:00:15.0155997 
    //5/25/2017 8:03:47 PM Finished 7 on thread 17, duration: 00:00:15.0155989 
    //5/25/2017 8:03:48 PM Finished 8 on thread 18, duration: 00:00:15.0155985 
    //5/25/2017 8:03:49 PM Finished 9 on thread 19, duration: 00:00:15.0156328 
    //5/25/2017 8:04:04 PM Finished, duration 00:00:21.0322775 
    //Press any key to exit. 
} 
+0

ありがとうございます@Clayと@Guilherme!両方の答えは素晴らしいです、それぞれは状況の詳細に基づいて使用することができます。私はこのフォーラムでどのように扱われるかを知らず、答えと有用性の両方をマークしました。 – tallman

答えて

1

非同期作業では、何かを待っているときに他のことを進めることができます...そして、それを行う方法は、時間を費やすことを待っていることです(何度もI/O)。それを実際に見るために、あなたは真に非同期のファッションを実行することができます。

static void Main(string[ ] args) 
{ 
    var totalTime = DoSomeAsyncTasks().GetAwaiter().GetResult(); 
    Console.WriteLine("{0} Finished, duration {1}", DateTime.Now, totalTime); 
    Console.WriteLine("Press any key to exit."); 
    Console.Read(); 
} 

async static Task<TimeSpan> DoSomeAsyncTasks() 
{ 
    var mainStart = DateTime.Now; 
    var tasks = new List<Task>(); 
    for (int i = 0; i < 10; i++) 
    { 
    var id = i; 
    tasks.Add(DoATask(id)); 
    } 
    await Task.WhenAll(tasks); 
    var mainFinish = DateTime.Now; 
    return mainFinish - mainStart; 
} 

static async Task DoATask(int stepId) 
{ 
    var stepStart = DateTime.Now; 

    Console.WriteLine(
    "{0} Starting {1} on thread {2}...", 
    stepStart, stepId, Thread.CurrentThread.ManagedThreadId); 

    //--> more accurately model waiting for I/O... 
    await Task.Delay(TimeSpan.FromSeconds(15)); 

    var stepFinish = DateTime.Now; 
    Console.WriteLine("{0} Finished {1} on thread {2}, duration: {3}", 
     stepStart, stepId, Thread.CurrentThread.ManagedThreadId, stepFinish - stepStart); 
} 

...返さ:たとえば

5/25/2017 11:24:36 PM Starting 0 on thread 9... 
5/25/2017 11:24:36 PM Starting 1 on thread 9... 
5/25/2017 11:24:36 PM Starting 2 on thread 9... 
5/25/2017 11:24:36 PM Starting 3 on thread 9... 
5/25/2017 11:24:36 PM Starting 4 on thread 9... 
5/25/2017 11:24:36 PM Starting 5 on thread 9... 
5/25/2017 11:24:36 PM Starting 6 on thread 9... 
5/25/2017 11:24:36 PM Starting 7 on thread 9... 
5/25/2017 11:24:36 PM Starting 8 on thread 9... 
5/25/2017 11:24:36 PM Starting 9 on thread 9... 
5/25/2017 11:24:36 PM Finished 9 on thread 11, duration: 00:00:15.0085175 
5/25/2017 11:24:36 PM Finished 8 on thread 12, duration: 00:00:15.0085175 
5/25/2017 11:24:36 PM Finished 7 on thread 13, duration: 00:00:15.0315198 
5/25/2017 11:24:36 PM Finished 6 on thread 14, duration: 00:00:15.0325121 
5/25/2017 11:24:36 PM Finished 5 on thread 12, duration: 00:00:15.0335121 
5/25/2017 11:24:36 PM Finished 3 on thread 11, duration: 00:00:15.0335121 
5/25/2017 11:24:36 PM Finished 2 on thread 12, duration: 00:00:15.0355229 
5/25/2017 11:24:36 PM Finished 1 on thread 11, duration: 00:00:15.0355229 
5/25/2017 11:24:36 PM Finished 4 on thread 14, duration: 00:00:15.0335121 
5/25/2017 11:24:36 PM Finished 0 on thread 13, duration: 00:00:15.0545213 
5/25/2017 11:24:51 PM Finished, duration 00:00:15.0665191 

(Iすべて同じもの、あなたのコードをした - と受け入れられた答えは...それを作るための方法にそれを広げましたもう少し具体的です)

これは、各タスクがほぼ同じ時間に開始され、各タスクが約15秒かかりましたが、合計ランタイムは約15秒でした。これは、タスクが待機している間に他の作業が実行されるためです。また、すべてが同じ単一のスレッドで実行されていることに注意してください。作品は待っていることによってインタリーブされます。それはかなり涼しく、おそらくあなたが期待していたかもしれないものにもっと似ています。

1

タスクはスレッドではありません。あなたがそれらのすべてのタスクを同時に並行して実行することを保証したい場合は、代わりにこれを試してみてください。

class Program 
{ 
    static void Main(string[] args) 
    { 
     var mainStart = DateTime.Now; 
     var threads = new List<Thread>(); 
     for (int i = 0; i < 10; i++) 
     { 
      threads.Add(new Thread(() => 
      { 
       var stepStart = DateTime.Now; 
       Console.WriteLine("{0} Starting {1} on thread {2}...", stepStart, i, Thread.CurrentThread.ManagedThreadId); 
       Thread.Sleep(15000); 
       var stepFinish = DateTime.Now; 
       Console.WriteLine("{0} Finished {1} on thread {2}, duration: {3}", 
        stepStart, i, Thread.CurrentThread.ManagedThreadId, stepFinish - stepStart); 
      })); 
     } 

     foreach (Thread t in threads) 
     { 
      t.Start(); // Starts all the threads 
     } 

     foreach(Thread t in threads) 
     { 
      t.Join(); // Make the main thread wait for the others 
     } 

     var mainFinish = DateTime.Now; 
     Console.WriteLine("{0} Finished, duration {1}", DateTime.Now, mainFinish - mainStart); 
     Console.WriteLine("Press any key to exit."); 
     Console.Read(); 
    } 
} 

の主な違いについてhere続きを読みます。

ジョンスキートが言うように:

スレッドは下位レベルの概念です:あなたが直接スレッドを開始している場合、あなたはそれがむしろなどスレッドプール上で実行するよりも、別のスレッドになります知っています

タスクは単に「どこでコードを実行するか」の抽象化だけではなく、実際には「将来の結果の約束」です。

0

Factory.StartNewは、単純にそのスレッドのすべてが、オットの仕事で忙しくしている場合は、必ずしもすぐにタスクを実行していない--it TaskSchedulerでタスクをエンキューします。開始時刻を記録すると、それらのタスクの少なくとも1つが6秒間開始しないことがわかります。

1

何が欠けていますか?

スレッドプールの仕組み。

スレッドプールは、実行する作業のキューとともに、スレッドのコレクションと考えることができます。通常、スレッドプールのスレッド集合はCPUコアの数とほぼ同じです。なぜなら、多数のスレッドだけが一度に実際に実行(つまり実行)できるからです。

さらに、スレッドプールはスレッドよりも多くの作業を行うと、そのスレッドコレクションにスレッドを追加します。しかし、それはスレッド注入率を制限します - IIRC、現在のスレッド注入率は2秒ごとに1つの新しいスレッドのようなものです。この制限はスレッドのスラッシングを防ぐために必要です。スレッドを作成して破棄するのにはコストがかかります。そのため、スレッドプールはヒューリスティックな注入率制限を使用します。

したがって、one answerは、プレーンスレッドを使用してスレッドプールを回避します(実世界コードでは決して推奨しません)。 Another answerは、非同期タスクを使用してスレッドプールを回避します。そして、この答えはなぜあなたはその動作を見ていた理由を説明します。

のスレッドプールに同時に(同期的に)実行したい場合は、スレッドプールをincrease its minimum number of threadsに伝えてください。