[C#] 37. スレッド(Thread)を使い方、Thread.Sleep関数を使い方


Study / C#    作成日付 : 2019/07/22 23:45:05   修正日付 : 2021/09/24 20:40:58

こんにちは。明月です。


この投稿はC#でスレッド(Thread)を使い方、Thread.Sleep関数を使い方に関する説明です。


我々がプログラムを作成して実行するとソースの順番とおりに実行されることが基本流れです。

これがプロセスということにします。つまり、プログラムが実行して終了するまでは一つのプロセスが実行することです。

using System;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // 繰り返しは0から9まで
      for (var i = 0; i < 10; i++)
      {
        // コンソール出力
        Console.WriteLine("a = " + i);
      }
      // 繰り返しは0から9まで
      for (var i = 0; i < 10; i++)
      {
        // コンソール出力
        Console.WriteLine("b = " + i);
      }
      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


上の例は0から9までの繰り返しを2回実行します。

同然にa =の出力が先に実行して終了すると、b =の出力が次に実行します。これが一つのプロセスです。


でも、仕様によりこの二つのfor文を並列で同時に実行したい時があります。

using System;
using System.Threading;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // 始めのスレッド生成
      var thread1 = new Thread(() =>
      {
        // 繰り返しは0から9まで
        for (var i = 0; i < 10; i++)
        {
          // コンソール出力
          Console.WriteLine("a = " + i);
        }
      });
      // 2つ目のスレッド生成
      var thread2 = new Thread(() =>
      {
        // 繰り返しは0から9まで
        for (var i = 0; i < 10; i++)
        {
          // コンソール出力
          Console.WriteLine("b = " + i);
        }
      });
      // 始めのスレッド実行
      thread1.Start();
      // 2つ目のスレッド実行
      thread2.Start();

      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


上の例をみれば始めのスレッドと2つ目のスレッドが同時に実行してコンソールに無作為で出力されることを確認できます。

つまり、上の例は一つのプロセスで2つのスレッドが実行したことです。3つの並列処理が実行されることです。

例を見れは"Press Any key..."が先に出力されることを確認できますが、これはスレッドを実行することに少しディレイが発生したのでプロセス処理が先に出力されてしまうことです。


スレッドを使う方法はThreadクラスのインスタンスを生成して、コンストラクタのパラメータは返却値とパラメータがないデリゲートで受け取ります。

using System;
using System.Threading;

namespace Example
{
  class Program
  {
    // スレッドで使う関数
    static void ThreadMethod1()
    {
      // 変数
      var sum = 0;
      // 繰り返しは0から99999まで
      for (var i = 0; i < 100000; i++)
      {
        // 変数に足す。
        sum += i;
      }
      // コンソール出力
      Console.WriteLine("Sum1 = " + sum);
    }
    // スレッドで使う関数
    static void ThreadMethod2()
    {
      // 変数
      var sum = 0;
      // 繰り返しは0から999まで
      for (var i = 0; i < 1000; i++)
      {
        // 変数に足す。
        sum += i;
      }
      // コンソール出力
      Console.WriteLine("Sum2 = " + sum);
    }
    // 実行関数
    static void Main(string[] args)
    {
      // スレッド生成
      var thread1 = new Thread(ThreadMethod1);
      var thread2 = new Thread(ThreadMethod2);
      // スレッド実行
      thread1.Start();
      thread2.Start();

      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


それでこのThread関数には変数を渡せないので、上のラムダ式を使ってクロージャ(Closure)機能を利用して変数を渡す方法をよく使います。

using System;
using System.Threading;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // 変数
      var sum = 0;
      // スレッド生成
      var thread1 = new Thread(() =>
      {
        // 繰り返しは0から999まで
        for (var i = 0; i < 1000; i++)
        {
          // 変数に足す。
          sum += i;
        }
      });
      // スレッド実行
      thread1.Start();
      // コンソール出力
      Console.WriteLine("Sum = " + sum);

      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


それで、上の例をみればThreadを使ってfor文で値を足したが、結果は0が出力されました。

理由はスレッドが終了する時まで待たなくて、プロセスが先に実行されるからです。つまり、コンソールが出力される時にはsumの変数の値が0からです。並列で実行されるのでコンソールが出力された後でsumの変数が変わりそうですね。


並列処理をすれば性能は速くなるけど、プロセスでスレッドの値をしっかり受け取ることができないと意味がありません。

using System;
using System.Threading;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // 変数
      var sum = 0;
      // スレッド生成
      var thread1 = new Thread(() =>
      {
        // 繰り返しは0から999まで
        for (var i = 0; i < 1000; i++)
        {
          // 変数に足す。
          sum += i;
        }
      });
      // スレッド実行
      thread1.Start();
      // スレッドが終了するまでプロセスを停止
      thread1.Join();
      // コンソール出力
      Console.WriteLine("Sum = " + sum);

      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


thread1変数でJoin関数を使ったらプロセスでスレッドが終了する時まで待ってる役割します。つまり、何個のスレッドを使ったらJoin関数を使ってプロセスと同期化するとプログラムを早く処理することができます。


また、仕様によりThreadをわざと処理を遅くなることもできます。

using System;
using System.Threading;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // スレッド生成
      var thread1 = new Thread(() =>
      {
        // 繰り返しは0から9まで
        for (var i = 0; i < 10; i++)
        {
          // 現在時間をコンソールに出力
          Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd hh:mm:ssss"));
          // 1秒待機
          Thread.Sleep(1000);
        }
      });
      // スレッド開始
      thread1.Start();
      // スレッドが終了するまでプロセス停止
      thread1.Join();

      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


Thread.Sleep()関数を利用してスレッドを1秒間に停止します。Sleep関数ではミリ秒単位で設定するので1000を入れると1秒になります。


スレッドはプロセスを並列処理が可能にする機能です。適切に使ったらプログラムの性能を飛躍的に上がることができます。

でも、スレッドというのは一つのリソースなので、限界があります。つまり、スレッドを無限に作成することで無限に早くなることではなく、.Net frameworkでスレッドリソースを管理するので、どのほど多くなると逆に遅くなります。

それで簡単な処理する計算式ならスレッドを生成して管理する部分でもっと遅くなるのでスレッドを使わない方が速いです。


プログラムでリソースを管理(ファイル管理、通信管理など)する処理かユーザのイベントを待つ処理などでスレッドをよく使います。

理由はプロセス速度とリソース間の速度差異があるのでそのギャップを減らすためによく使います。


ここまでC#でスレッド(Thread)を使い方、Thread.Sleep関数を使い方に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

#C#
最新投稿