C# – 細かく「delegate」Part.4

Part.1はこちら
Part.2はこちら
Part.3はこちら
Part.4(この記事)

delegateができること

非同期処理

Part.2で述べたようにデリゲートの型を定義すると、共通言語ランタイムよりBeginInvokeとEndInvokeというメソッドが提供される。
BeginInvokeの戻り値は「System.IAsyncResult」で、
EndINvokeの戻り値は「デリゲートの型の戻り値」になる。

ただ、これらの引数は定義したデリゲートの型に依存し変化する。
.NETのドキュメントによると、

BeginInvokeの引数は、デリゲートの型の引数+2つの引数。
2つの引数の1番目は、System.AsyncCallBack Delegate型。
2つの引数の2番目は、Object型。
定義したデリゲート型の引数がなければ、上記2つの引数のみとなる。

EndInvokeの引数は、デリゲートの型の参照渡し引数+System.IAsyncResult。
定義したデリゲート型の引数にrefキーワードやoutキーワードを用いていなければSystem.IAsyncResultのみとなる。

無理矢理に定義を考えると、

IAsyncResult BeginInvoke([引数の型 引数[, ...],] AsyncCallback callback, object obj);
戻り値の型 EndInvoke([参照渡しの引数の型 引数[, ...],] IAsyncResult result);

という感じになる、のかな。

そして、BeginInvokeで非同期処理を開始して、その戻り値をEndInvokeに入れてやるとBeginInvokeの処理終了を待機するようだ。

例えば、

public class DelegateSample04 {
    //SampleDelegate型の定義
    delegate void SampleDelegate(string str);
    //SampleDelegate型のsample変数の宣言
    static SampleDelegate sample;
    
    public static void Main(){
        var text = "出力して欲しい物";
        //sample変数にstatic void WriteTenTimes(string)メソッドの代入
        sample = WriteTenTimes;
        //sample変数に代入されたメソッドを非同期的に呼出し
        var iar = sample.BeginInvoke(text,null,null);
        //メインでは「並列+今の回数」を5回出力する
        for(var i=0;i<5;i++)
            System.Console.WriteLine("並列" + i);
        //非同期処理終了待機
        sample.EndInvoke(iar);

        System.Console.WriteLine("終わり");
    }
    //「引数+今の回数」を10回出力するメソッド
    public static void WriteTenTimes(string text){
        for(var i=0;i<10;i++)
            System.Console.WriteLine(text + i);
    }
}

出力:※環境により変化します

並列0
出力して欲しい物0
出力して欲しい物1
出力して欲しい物2
並列1
並列2
出力して欲しい物3
出力して欲しい物4
並列3
並列4
出力して欲しい物5
出力して欲しい物6
出力して欲しい物7
出力して欲しい物8
出力して欲しい物9
終わり

といった形である。このコードに意味はないが、要点は2つ。
①「並列+回数」と「出力して欲しい物+回数」が混合して出力されている点。ここから、12行目のBeginInvokeの出力と、メインの14-15行目の出力が、非同期処理できているからである。
②メインの方の出力処理が先に終わっても、メイン処理は17行目で待機して最後に「終わり」を出力させることが出来ている点。

またEndInvokeで待機せずとも、BeginInvokeの戻り値を使って、

public class DelegateSample04plus {
    //SampleDelegate型の定義
    delegate void SampleDelegate(string str);
    //SampleDelegate型のsample変数の宣言
    static SampleDelegate sample;
    
    public static void Main(){
        var text = "Load処理";
        //SampleDelegate型のsample変数にstatic void WriteTenTimes(string)メソッドの代入
        sample = WriteTenTimes;
        //sample変数に代入されたメソッドを参照呼出し
        var iar = sample.BeginInvoke(text,null,null);
        //カウンタ変数
        var count = 0;
        //IAsyncResult.IsCompletedプロパティを確認し非同期処理が終わるまでループ
        while(!iar.IsCompleted)
            System.Console.WriteLine("なうろーでぃんぐ" + count++);
        
        System.Console.WriteLine("終わり");
    }
    public static void WriteTenTimes(string text){
        for(var i=0;i<10;i++)
            System.Console.WriteLine(text + i);
    }
}

出力:※環境により変化します

なうろーでぃんぐ0
Load処理0
なうろーでぃんぐ1
なうろーでぃんぐ2
Load処理1
Load処理2
なうろーでぃんぐ3
なうろーでぃんぐ4
Load処理3
Load処理4
なうろーでぃんぐ5
なうろーでぃんぐ6
Load処理5
Load処理6
なうろーでぃんぐ7
なうろーでぃんぐ8
Load処理7
Load処理8
Load処理9
なうろーでぃんぐ9
終わり

のようなこともできます。ロード画面処理とデータロード処理的な?

さて、12行目を見るとBeginInvokeの2つの引数はnullです。
二つの引数は.NETのドキュメントによると、

1番目の引数AsyncCallBackは、非同期処理が終わった後に呼び出される。
2番目の引数Objectは、呼び出されるAsyncCallBackに送るオブジェクト。

ということらしい。
僕には素晴らしい用途は思いつかないが、

public class DelegateSample04alpha {
    //SampleDelegate型の定義
    delegate void SampleDelegate(string str);
    //SampleDelegate型のsample変数の宣言
    static SampleDelegate sample;
    
    public static void Main(){
        var text = "出力して欲しい物";
        var endText = "非同期処理は終わりました";
        //sample変数にstatic void WriteTenTimes(string)メソッドの代入
        sample = WriteTenTimes;
        //sample変数に代入されたメソッドを非同期的に呼出し
        var iar = sample.BeginInvoke(text,WriteEnd,endText);
        //メインでは「並列+今の回数」を5回出力する
        for(var i=0;i<5;i++)
            System.Console.WriteLine("並列" + i);
        //非同期処理終了待機
        sample.EndInvoke(iar);

        System.Console.WriteLine("全体の終わり");
    }
    //「引数+今の回数」を10回出力するメソッド
    public static void WriteTenTimes(string text){
        for(var i=0;i<10;i++)
            System.Console.WriteLine(text + i);
    }
    //非同期処理終了時に呼び出されるメソッド
    public static void WriteEnd(System.IAsyncResult result){
        var endText = result.AsyncState as string;
        System.Console.WriteLine(endText);
    }
}

出力:※環境により変化します

出力して欲しい物0
並列0
出力して欲しい物1
出力して欲しい物2
並列1
並列2
出力して欲しい物3
出力して欲しい物4
並列3
並列4
出力して欲しい物5
出力して欲しい物6
出力して欲しい物7
出力して欲しい物8
出力して欲しい物9
非同期処理は終わりました
全体の終わり

で動作を確認することはできたが、このコードに意味はない。
重要なのは、29行目でIAsyncResult.AsyncStateプロパティで受け取り、asキャストでstringを取り出している点。
IAsyncResult.AsyncStateでBeginInvokeの最後の引数を受け取るのだが、Object型として渡されているので利用したい型(今回はstring)にキャストしてやる必要がある。

次回へ続く

カテゴリーC#

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です