C#でマルチスレッドプログラミング−2

デリゲートを使ったマルチスレッド(BeginInvoke, EndInvoke)

参考LINKの同期メソッドの非同期呼び出し | Microsoft Docsによるとかなり色々できるな…とりあえずわかったことを箇条書きでまとめる。

  • 内部的にはThreadpoolを使っている
  • Threadクラス・Threadpoolクラスを使ったものより書き方が簡単なので使い勝手がよさそう
  • BeginInvokeで非同期処理開始
  • BeginInvokeはIAsyncResult を返却。これを使用して非同期呼び出しの進捗状況を監視
  • IAsyncResult.AsyncWaitHandle.WaitOneで別スレッドの処理を指定時間だけ待つ
  • IAsyncResult.IsCompletedで別スレッドでの処理が終了してるか否かを取得できる
  • EndInvokeで非同期呼び出しの結果を取得。非同期呼び出しが完了していない場合、完了するまで呼び出し元をブロック
  • AsyncCallbackを使うとコールバック関数を定義できる
ケース1(シンプルケース)

まず一番シンプルに書くとこんな感じか。指定した数までの和を計算する処理を別スレッドで実行している。

using System;
using System.Threading;

class Program
{
    //非同期実行するためのデリゲート
    public delegate int AsyncMethodCaller(int x);

    //指定した数までの和を計算する関数
    static int Sum(int x)
    {
        //わざと処理を遅らせてる
        Thread.Sleep(5000);

        int result = 0;
        for(int i = 0; i <= x; i++)
        {
            result += i;
        }
        return(result);
    }
    static void Main(string[] args)
    {
        //メインスレッドスタート&スレッドIDの表示
        Console.WriteLine("Main Thread Start. Thread ID : {0}", Thread.CurrentThread.ManagedThreadId);

        //実行するデリゲートを作成
        AsyncMethodCaller caller = new AsyncMethodCaller(Sum);

        //非同期実行の呼び出し
        IAsyncResult result = caller.BeginInvoke(10, null, null);

        //メインスレッドでのなんか処理


        //計算結果の取得
        int sum = caller.EndInvoke(result);

        Console.WriteLine("The calculation result : {0}", sum);
        Console.WriteLine("Main thread exits.");
    }
}

実行結果

Main Thread Start. Thread ID : 1
The calculation result : 55
Main thread exits.
ケース2(処理状況を監視しながらの実行)

WaitHandleやIsCompletedを使って処理状況をチェックしながらの実行版

using System;
using System.Threading;

class Program
{
    //非同期実行するためのデリゲート
    public delegate int AsyncMethodCaller(int x);

    //指定した数までの和を計算する関数
    static int Sum(int x)
    {
        //わざと処理を遅らせてる
        Thread.Sleep(5000);

        int result = 0;
        for (int i = 0; i <= x; i++)
        {
            result += i;
        }
        return (result);
    }
    static void Main(string[] args)
    {
        //メインスレッドスタート&スレッドIDの表示
        Console.WriteLine("Main Thread Start. Thread ID : {0}", Thread.CurrentThread.ManagedThreadId);

        //実行するデリゲートを作成
        AsyncMethodCaller caller = new AsyncMethodCaller(Sum);

        //非同期実行の呼び出し
        IAsyncResult result = caller.BeginInvoke(10, null, null);
        //1秒だけ現在の処理を中断して別スレッドの計算を待つ
        result.AsyncWaitHandle.WaitOne(1000);
        //もし別スレッドでの計算が終わったならif以下の処理。条件を満たさないのでここは実行されない
        if (result.IsCompleted)
        {
            Console.WriteLine("this section is never run.");
        }

        //別スレッドの処理が終わるまで待つ
        result.AsyncWaitHandle.WaitOne();
        if (result.IsCompleted)
        {
            Console.WriteLine("this section is run.");
            Console.WriteLine("The calculation result : {0}", caller.EndInvoke(result));
        }

        Console.WriteLine("Main thread exits.");
    }
}

実行結果

Main Thread Start. Thread ID : 1
The calculation result : 55
Main thread exits.
ケース3(コールバック関数を使用した方法)

AsyncCallback(CallbackFunc)としてスレッド終了後の処理を記述しているのがミソ。バックグランドで実行されるのでメインスレッド上でうまく計算終了を検知しないとだめ。

using System;
using System.Threading;

class Program
{
    //非同期実行するためのデリゲート
    public delegate int AsyncMethodCaller(int x);

    //指定した数までの和を計算する関数
    static int Sum(int x)
    {
        //わざと処理を遅らせてる
        Thread.Sleep(5000);

        int result = 0;
        for (int i = 0; i <= x; i++)
        {
            result += i;
        }
        return (result);
    }
    static void CallbackFunc(IAsyncResult ar)
    {
        AsyncMethodCaller caller = (AsyncMethodCaller)ar.AsyncState;
        Console.WriteLine("The calculation result : {0}", caller.EndInvoke(ar));
    }
    static void Main(string[] args)
    {
        //メインスレッドスタート&スレッドIDの表示
        Console.WriteLine("Main Thread Start. Thread ID : {0}", Thread.CurrentThread.ManagedThreadId);

        //実行するデリゲートを作成
        AsyncMethodCaller caller = new AsyncMethodCaller(Sum);

        //非同期実行の呼び出し
        //最後の引数のcallerがIAsyncResult.AsyncStateとしてコールバック関数へと渡る
        IAsyncResult result = caller.BeginInvoke(10, new AsyncCallback(CallbackFunc), caller);

        //別スレッドが実行されてる間はここで適当に待機
        while (result.IsCompleted == false)
        {
            Thread.Sleep(10);
        }

        Console.WriteLine("Main thread exits.");
    }
}

実行結果

Main Thread Start. Thread ID : 1
The calculation result : 55
Main thread exits.