Taskでスレッド作り過ぎ遊びで遊んでいました

環境

を利用しています。その他の環境では確認しておりませんのでご了承下さい。

Taskって便利ですよね

ここでいうTaskは、.NET Framework4.0で追加された非同期処理ライブラリのTaskのことです。
Taskを使うと非同期処理を凄く簡単になるのでみんなも使うと良いと思います。

Taskがよくわからない人はxin9leさんのブログを見て勉強すると分かりやすいです。

Taskでスレッド作り過ぎ遊び

んで、こんなに簡単にスレッド作れると嬉しくなるので、みんなもどんどんスレッド作りすぎると良いと思います。

    private void foo()
    {
      foreach (var i in Enumerable.Range(0, 1000))
      {
        var local = i;
        Task.Factory.StartNew(() =>
        {
          Console.WriteLine("{0}:{1}", Thread.CurrentThread.ManagedThreadId, local);
        });
      }
      Console.WriteLine("Done!");
    }

んでこれの出力はこんなふうになるんですが、

3:0
3:2
Done!
16:1
3:3
3:5
3:6
3:7
3:8
3:9
3:10
3:11
3:12
...

非同期処理の中身がすぐに終わる処理なので同じスレッドが使い回されています。


あと、

var local = i;

とかなんでこんなことが必要なのか分からない人は、これでも読んで勉強するといいですよ。


んで、非同期処理の中には無限ループとキャンセル待ちの処理も多くあると思うんですが、

    private void foo()
    {
      var tcs = new CancellationTokenSource();
      var token = tcs.Token;

      foreach (var i in Enumerable.Range(0, 1000))
      {
        var local = i;
        Task.Factory.StartNew(() =>
        {
          Console.WriteLine("{0}:{1}", Thread.CurrentThread.ManagedThreadId, local);
          while (true)
          {
            Thread.Sleep(5000); //heavy process
            if (token.IsCancellationRequested)
            {
              break;
            }
          }
        });
      }
      Console.WriteLine("Done!");
    }

これを実行すると、

10:0
Done!
11:1
12:2
13:3

みたいな感じで全部違うスレッドで処理されてスレッド無駄遣いしてる感じがすげー楽しい!俺は富豪だ!リッチプログラミング!


あと、この辺りのキャンセル処理が分からない人は、MSDN

を読むと良いですよ。

でも一つ景気よくないことがある

でも嬉しくないことがあって、上のコードを実行するとTaskが生成されるのは凄い速いんだけど実際のスレッドが作られるのは結構時間がかかっているってことです。
だいたい一個のスレッドが生成されるのに1秒くらいかかってます。


これにはスレッドプールが関係していて、Taskが生成されると裏ではスレッドプールにキューイングされることに起因するものでしょう。

スレッドプールってのは言葉で表現するのは難しいので図解するとこうなります。

なんであれ、これでは俺は嬉しくない。俺はこんな真綿で首を絞めるような感じではなくて、宝くじに当たったら三日で破産するような浪費がしたいんだ!



ThradPoolの設定を変えよう

Taskが裏でThreadPoolに操られているならThreadPoolの設定を変えてしまえばよいのです!

    private void foo()
    {
      int minWorkerThread, minCompletionPortThread;
      ThreadPool.GetMinThreads(out minWorkerThread, out minCompletionPortThread);
      ThreadPool.SetMinThreads(1000, minCompletionPortThread);
      var tcs = new CancellationTokenSource();
      var token = tcs.Token;

      foreach (var i in Enumerable.Range(0, 1000))
      {
        var local = i;
        Task.Factory.StartNew(() =>
        {
          Console.WriteLine("{0}:{1}", Thread.CurrentThread.ManagedThreadId, local);
          while (true)
          {
            Thread.Sleep(5000); //heavy process
            if (token.IsCancellationRequested)
            {
              break;
            }
          }
        });
      }
      Console.WriteLine("Done!");
    }

これで実行したとたんに大量のスレッドが動き出しました。実に無駄遣いです!
ちなみに僕の環境では、ThreadPool.SetMinThreads(,)のworkerThreadに1024以上の値を入れると失敗しました。残念です。
あと、ThreadPoolの設定を変えるとパフォーマンスに影響を与えるので気をつけてください。

上限も変えてしまえ

じゃあもっと無駄遣いしようってなって、1000とかしゃらくせえこと言うのはやめて4000だ4000!ってなりました。

    private void foo()
    {
      int minWorkerThread, minCompletionPortThread, maxWorkerThread, maxCompletionPortThread;
      ThreadPool.GetMinThreads(out minWorkerThread, out minCompletionPortThread);
      ThreadPool.SetMinThreads(1000, minCompletionPortThread);
      ThreadPool.GetMaxThreads(out maxWorkerThread, out maxCompletionPortThread);
      ThreadPool.SetMaxThreads(5000, maxCompletionPortThread);
      var tcs = new CancellationTokenSource();
      var token = tcs.Token;

      foreach (var i in Enumerable.Range(0, 4000))
      {
        var local = i;
        Task.Factory.StartNew(() =>
        {
          Console.WriteLine("{0}:{1}", Thread.CurrentThread.ManagedThreadId, local);
          while (true)
          {
            Thread.Sleep(5000); //heavy process
            if (token.IsCancellationRequested)
            {
              break;
            }
          }
        });
      }
      Console.WriteLine("Done!");
    }

SetMaxThreadはtrueを返したんですが、実行時にThreadが1200くらいになって以降スレッドが生成されませんでした。スレッドが枯渇したんでしょうかね。
んでもってやっぱりパフォーマンスに影響を与えるので気をつけてください。

Task楽しいよTask

僕はここ数日狂ったようにTaskでスレッドを作りまくって遊んでいたんですが、Taskはいいですね。
Taskを覚えてしまうとThreadPoolとかThreadとかを使うのが面倒くさくてしょうがなくなるのでみんなもTaskとかで富豪みたいに遊ぶと良いですよ!