« はじめまして | トップページ | アプリケーション固有のデータ格納先 »

2010年9月16日 (木)

非同期デリゲートの使用法

非同期デリゲートを使うと、ちょっとした事をお手軽にバックグラウンド処理が出来る。
そう思っていざ使ったところ、はまった事があったので、非同期デリゲートについてまとめておく。

最も基本的なポイント

  1. 非同期で呼び出すメソッドと同じシグネチャを持つデリゲートを用意する。
  2. 用意したデリゲートから BeginInvoke メソッドにより、非同期実行を開始する。
  3. BeginInvoke 指定の際、非同期処理が終了したときのメソッドを指定するが、ここで EndInvoke メソッドを必ず実行する。

実際のコードは、だいたいこんな感じ。


using System;

namespace ConsoleApplication1
{
    delegate void AsyncDelegate(string msg);
    /// <summary>
    /// アプリケーション主実行クラス
    /// </summary>
    class Ap
    {
        [STAThread]
        static void Main(string[] args)
        {
            Console.WriteLine("主処理を開始しました。");
            AsyncTest at = new AsyncTest();
            Helper helper = new Helper();
            helper.asyncProc = new AsyncDelegate(at.OutputMessage);
            IAsyncResult iar = helper.LongProc();
            System.Threading.Thread.Sleep(1000);
            while (!iar.IsCompleted)
            {
                Console.Write(".");
                System.Threading.Thread.Sleep(1000);
            }
            Console.WriteLine("主処理を終了しました。");
        }
    }
    /// <summary>
    /// 非同期デリゲートを実行するメソッドがあるクラス
    /// </summary>
    class AsyncTest
    {
        internal void OutputMessage(string msg)
        {
            Console.WriteLine("OutputMessage処理を開始しました。msg = {0}", msg);
            System.Threading.Thread.Sleep(3000);    // 3秒待つ
            Console.WriteLine("\nOutputMessage処理を終了しました。");
        }
    }
    /// <summary>
    /// 非同期実行を行う為のヘルパークラス
    /// </summary>
    class Helper
    {
        internal AsyncDelegate asyncProc;
        internal IAsyncResult LongProc()
        {
            Console.WriteLine("LongProc処理を開始しました。");
            IAsyncResult ias = asyncProc.BeginInvoke("受け渡し文字列"
                  new AsyncCallback(EndLongProc), asyncProc);
            Console.WriteLine("LongProc処理を終了しました。");
            return ias;
        }
        internal void EndLongProc(IAsyncResult ar)
        {
            AsyncDelegate ad = (AsyncDelegate)ar.AsyncState;
            ad.EndInvoke(ar);
        }
    }
}

しかし、非同期処理で複数の変数を設定して返したいときは、どうする?(だったと思う。過去の事なので)
1つなら、非同期メソッドの戻り値で対処可能である。ちなみにこの場合は、EndInvoke で戻り値が得られる。
上記例なら、Helper クラスの EndLongProc メソッド中にある、EndInvoke で、

int result = ad.EndInvoke(ar);

とかすれば良い。その場合、AsyncDelegate や AsyncTest クラスの OutputMessage も戻り値の型 int に変更する事は言うまでもない。

さて本当の目的、複数の変数設定値を受け取る場合の記述である。
これは、AsyncTest クラスの OutputMessage のパラメータに、ref または out 指定で引数を入れる事により、BeginInvoke だけでなく EndInvoke もそのパラメータを指定する様になっていた。つまり、

internal int OutputMessage(string msg, ref int code, out string status)

に変更すると、BeginInvoke,EndInvoke はそれぞれ、

IAsyncResult ias = asyncProc.BeginInvoke("受け渡し文字列",
    ref code, out status, new AsyncCallback(EndLongProc), asyncProc);
int result = ad.EndInvoke(ref code, out status, ar);

といった具合に、指定するパラメータが増える。
EndInvoke は、ref/out 指定の変数だけパラメータを要求するってのがミソだった。

最後に、この最終的な形のテストソースを載せておく。


using System;

namespace ConsoleApplication1
{
    delegate int AsyncDelegate(string msg, ref int code, out string status);
    /// <summary>
    /// アプリケーション主実行クラス
    /// </summary>
    class Ap
    {
        [STAThread]
        static void Main(string[] args)
        {
            Console.WriteLine("主処理を開始しました。");
            AsyncTest at = new AsyncTest();
            Helper helper = new Helper();
            helper.asyncProc = new AsyncDelegate(at.OutputMessage);
            IAsyncResult iar = helper.LongProc();
            System.Threading.Thread.Sleep(1000);
            while (!iar.IsCompleted)
            {
                Console.Write(".");
                System.Threading.Thread.Sleep(1000);
            }
            Console.WriteLine("主処理を終了しました。");
        }
    }
    /// <summary>
    /// 非同期デリゲートを実行するメソッドがあるクラス
    /// <summary>
    class AsyncTest
    {
        internal int OutputMessage(string msg, ref int code, out string status)
        {
            DateTime dt = DateTime.Now;
            Console.WriteLine("OutputMessage処理を開始しました。msg = {0}", msg);
            System.Threading.Thread.Sleep(3000);    // 3秒待つ
            Console.WriteLine("\nOutputMessage処理を終了しました。");
            code = 1200;
            status = "done";
            return 2;
        }
    }
    /// <summary>
    /// 非同期実行を行う為のヘルパークラス(クラスを別にする必要はない)
    /// </summary>
    class Helper
    {
        internal AsyncDelegate asyncProc;
        internal IAsyncResult LongProc()
        {
            int code = 0;
            string status;
            Console.WriteLine("LongProc処理を開始しました。");
            IAsyncResult ias = asyncProc.BeginInvoke("受け渡し文字列",
                ref code, out status, new AsyncCallback(EndLongProc), asyncProc);
            Console.WriteLine("LongProc処理を終了しました。");
            return ias;
        }
        internal void EndLongProc(IAsyncResult ar)
        {
            int code = 0;
            string status;
            AsyncDelegate ad = (AsyncDelegate)ar.AsyncState;
            int result = ad.EndInvoke(ref code, out status, ar);
            Console.WriteLine("OutputMessage処理戻り値:{0},code={1},status={2}",
                result, code, status);
        }
    }
}

« はじめまして | トップページ | アプリケーション固有のデータ格納先 »

C# Tips」カテゴリの記事

コメント

コメントを書く

コメントは記事投稿者が公開するまで表示されません。

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: 非同期デリゲートの使用法:

« はじめまして | トップページ | アプリケーション固有のデータ格納先 »