いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

タスク並列ライブラリ入門記-006 (TaskCreationOptions.LongRunning, 長時間実行されるタスクであることを示すオプション, オーバーサブスクリプション)

Taskには、作成時にTaskCreationOptionsを指定することができます。
その中に、

TaskCreationOptions.LongRunning

という項目があります。文字通り長時間処理されるタスクの場合に
指定する項目なのですが、これを指定すると場合によっては、タスクスケジューラが
スレッドプールスレッドを利用せずにタスクを実行することがあります。

あまり利用する事はありませんが、サンプル作ったのでついでなのでアップしました。
以下、サンプルです。

namespace Sazare.Samples
{
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Threading;
  using System.Threading.Tasks;

  using Sazare.Common;

  /// <summary>
  /// タスク並列ライブラリについてのサンプルです。
  /// </summary>
  /// <remarks>
  /// タスク並列ライブラリは、.NET 4.0から追加されたライブラリです。
  /// </remarks>
  [Sample]
  class TaskSamples06 : IExecutable
  {
    public void Execute()
    {
      //
      // タスクには作成時にTaskCreationOptionsを
      // 指定することができる。このオプションの中に
      //   TaskCreationOptions.LongRunning
      // という項目がある。LongRunningを指定すると
      // このタスクは、通常よりも長い間処理が続く
      // ということをタスクスケジューラに通知することに
      // なる。
      //
      // LongRunningと指定されたタスクは場合によって
      // スレッドプールスレッドを利用せずに実行される。
      // (長時間の処理で、かつ、ブロックする場合など)
      //
      // LongRunningを指定するとタスクスケジューラに対して
      // オーバーサブスクリプションを許可することになる。
      //
      // なので、一つや二つなどの場合はいいが
      // たくさんのタスクをLongRunningさせるべきではない。
      // (長時間かかる処理を producer/consumerパターンを
      //  利用して実装するなどの工夫が必要。)
      //

      //
      // 処理前のスレッドプールのスレッド数を表示.
      //
      Output.WriteLine("Before Task running...");
      PrintAvailableThreadPoolCount();

      //
      // 通常のタスクを開始し、その後スレッド数を表示.
      //
      var task1StartSignal = new ManualResetEventSlim(false);
      var task1 = Task.Run(() =>
        {
          task1StartSignal.Set();
          Output.WriteLine("Normal Task...");
          SpinWait.SpinUntil(() => false, 5000);
        }
      );

      task1StartSignal.Wait();
      Output.WriteLine("After Task running...");
      PrintAvailableThreadPoolCount();

      //
      // TaskCreationOptions.LongRunningを
      // 指定して、タスクを開始.
      // (TaskCreationOptionsはTask.Runメソッドで指定できないので注意)
      //
      var task2StartSignal = new ManualResetEventSlim(false);
      var task2 = Task.Factory.StartNew(() =>
        {
          task2StartSignal.Set();
          Output.WriteLine("LongRunning Task....");
          SpinWait.SpinUntil(() => false, 5000);
        },
        TaskCreationOptions.LongRunning
      );

      task2StartSignal.Wait();
      Output.WriteLine("After LongRunning Task running...");
      PrintAvailableThreadPoolCount();

      //
      // 終了待ち.
      //
      Task.WaitAll(task1, task2);
    }

    internal void PrintAvailableThreadPoolCount()
    {
      int availableWorkerThreadsCount;
      int availableIOThreadsCount;

      ThreadPool.GetAvailableThreads(
        out availableWorkerThreadsCount, 
        out availableIOThreadsCount);

      Output.WriteLine(
        string.Format(
          "\tWorker Threads: {0}, IO Threads: {1}", 
          availableWorkerThreadsCount, 
          availableIOThreadsCount));
    }
  }
}

実行結果は、以下のようになります。

================== START ==================
Before Task running...
        Worker Threads: 1023, IO Threads: 1000
Normal Task...
After Task running...
        Worker Threads: 1022, IO Threads: 1000
LongRunning Task....
After LongRunning Task running...
        Worker Threads: 1022, IO Threads: 1000
==================  END  ==================

結果をみると、通常のタスクが実行されるとスレッドプールのスレッド数が 一つ減っているのに対して、LongRunningを指定して実行した場合 数が減っていません。(常にこうなるとは限りませんのでご注意を)


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。