C#でのasync&awaitとTaskの使い方と非同期の考え方をわかりやすく解説#3

当サイトではアフィリエイト広告を利用しています。

C#での非同期プログラミング

ThreadPool編

ThreadPoolとは何か?

ThreadPoolは前述のThreadクラスと違って,処理するたびにスレッドを生成せず,使えるスレッドがあればそこで処理するといったスレッドのリサイクルを行いながら処理をするので非常に効率のよいスレッドの使い方ができます。

ThreadPoolを使うとどんな良いことがあるのか?

毎回新たなスレッドを生成するThreadクラスだと新たなスレッドどんどん増えて行きます。同時に動作するスレッドが多いと,パソコンはあたかも同時進行しているようにそのすべてのスレッドを少しずつ処理しては切り替えるということを繰り返して,見た目上の平行処理を行います。

このスレッドとスレッドを切り替えて行く処理というのが,実はコストのかかる処理であるといわれています。要するに,スレッドが多いとパソコンは切り替えが大変で,パフォーマンスも悪くなります。よってできるだけ少ないスレッドのほうがよいことになります。

ThreadPool(スレッドプール)で非同期処理を実行すると,毎回スレッドを生成するのではなく,そのときに空き状態のスレッドを見つけてそこで処理することにより,スレッドの切り替えが最小限ですみます。だからひとつのスレッドで占有してずっと監視処理をする等でない限り,極力ThreadPoolを使用したほうがよいです。

後述するTaskはこのThreadPoolを使って,コーディング上では書きやすいようになっているので.NETFramework4.0以降を使用する場合はTaskを使いましょう。

ThreadPoolを使わないとどんな悪いことがあるのか?

ThreadPoolを使用しないデメリットは,前述のとおり,スレッドのリサイクルをしないことになるので,非常にコストの悪いプログラミングになります。

TreadPoolの使い方

それでは前述の5秒かかる検索処理のサンプルをThreadPoolにした実装を紹介します。

public partial class ThreadForm3 : Form
{
    private int _count = 0;
    public ThreadForm3()
    {
        InitializeComponent();
    }

    private void CountButton_Click(object sender, EventArgs e)
    {
        this.CountButton.Text = _count++.ToString();
    }

    /// <summary>
    /// ThreadPoolボタンクリック
    /// </summary>
    /// <param name="sender">コントロール</param>
    /// <param name="e">イベント引数</param>
    private void ThreadPoolButton_Click(object sender, EventArgs e)
    {
        ThreadPool.QueueUserWorkItem(GetDataAsync);
    }

    /// <summary>
    /// 検索処理
    /// </summary>
    /// <param name="o">オブジェクト引数</param>
    private void GetDataAsync(object o)
    {
        var dtos = new List<DTO>();
        for (int i = 0; i < 5; i++)
        {
            Thread.Sleep(1000);
            dtos.Add(new DTO(i.ToString(), DateTime.Now.ToString("HH:mm:ss")));
        }

        this.Invoke((Action)delegate()
        {
            dataGridView1.DataSource = dtos;
        });
    }
}
ThreadPoolButton_Clickイベント

ThreadPoolボタンのクリックイベントで次のように記述しています。

ThreadPool.QueueUserWorkItem(GetDataAsync);

QueueUserWorkItemの引数に非同期で動作させたい関数を指定します。Threadクラスと同じ用に,指定する関数を戻り値がなしのvoid,引数はobject型をひとつ指定します。object型の引数は,呼び出し側では省略できるため,上記書き方が可能です。指定する場合はThreadPool.QueueUserWorkItem(GetDataAsync, null);のように記述し,nullの部分が任意のクラスになります。型がobject型のため,どんなクラスでも指定できます。GetDataAsync関数側では,objectで受け取ったあと,任意のクラスに変換して使用する必要があります。

GetDataAsync関数

GetDataAsyncは前述のThreadクラスの時とほとんど同じです。異なるのは引数にobject型の引数を受け取れるようになっていることです。Thread内で判断処理などが必要な場合などは任意の引数クラスを作成し,判断したりするときに使用することができます。

そのほかはThreadクラスのときと同じですね。引数も指定できて,書き方も同じ用に書けるため,ThreadクラスよりもThreadPoolを使用するようにしましょう。