C# Programming

Image非同期プログラミング

開発環境: Visual Studio 2003

目次

  1. 目次
  2. 目的
  3. スレッドによる非同期プログラミング
  4. Windows.Forms.Control における非同期プログラミング機能
    1. Windows.Forms.Control における非同期プログラミングのデザインパターン
    2. 参考書
    3. Windows.Forms.Control における非同期プログラミング(BeginInvoke、WaitHandle、EndInvoke)
  5. 非同期デリゲートによるプログラミング
    1. 非同期デリゲート のデザインパターン
    2. 参考書
    3. 非同期デリゲート(コールバック)
    4. 非同期デリゲート(コールバック)のソースコード
    5. 非同期デリゲート(BeginInvoke, EndInvoke)
    6. 非同期デリゲート(BeginInvoke, EndInvoke)のソースコード
    7. 非同期デリゲート(ポーリング)
    8. 非同期デリゲート(ポーリング)のソースコード
    9. 非同期デリゲート(BeginInvoke、WaitHandle、EndInvoke の使用)
    10. 非同期デリゲート(BeginInvoke、WaitHandle、EndInvoke の使用)のソースコード

目的

非同期プログラミングには、今把握している限りでは3つの方法があります。

方法特徴スレッドプール
スレッドを使うきめ細かな処理が可能。ThreadPool クラスを利用して明示的にプログラミングする必要がある。
Windows.Forms.Control の非同期プログラミングWindows.Forms.Control はスレッドセーフではないので、
Forms.Control の非同期プログラミングの機構を使うことにより
安全な非同期プログラミングをすることができる。

以下のクラスライブラリでも、同様な機構が提供されている。
HttpSimpleClientProtocol
LogicalMethodInfo
SoapHttpClientProtocol
スレッドプールは使われないようだ。
非同期デリゲートによるプログラミングデリゲートを使用する点は、2の方法と同じように見えるが、
この方法はコンパイラー、コモンランゲージランタイムにより直接サポートされているもので、
仕組みとしてはまったく別物と考えたほうが良い。

クラスライブラリでサポートされているのではないので、
インテリセンスによるヘルプが効かない。
自動的にスレッドプールが使われる。
このため、スレッドプールの動作を念頭においておかないと、
処理が遅い、デッドロックなどの不可解な現象が発生する
可能性があります。

1. のThread を使うケースは、どこにでもある方法だし、あちこちに解説があるので問題ないでしょう。
しかし、2、3の 非同期プログラミングは、ほとんど情報がないし、VisualStudio.NET のヘルプの解説が最悪で、読み解くのにす〜〜っごく苦労しました。たぶん、翻訳(監訳)した人も何のことだか理解してないのでしょう。

非同期プログラミングは避けては通れないので、とにかくわかった範囲でメモっておきます。

3.スレッドによる非同期プログラミング

using System;
using System.Threading;

public class Simple
{
        public class MyThread
        {
                // Threadは sealed classなので、継承できない。
                // Threadのdelegateにより呼び出す。
                private int number;
                public MyThread( int no )
                {
                        number = no;
                }

                public void Run()
                {
                        while (true)
                        {
                                Console.WriteLine("Thread Sleep = {0}", number);
                                Thread.Sleep(number);
                        }
                }
        }

        public static void Main()
        {
                // ThreadからたたかれるMyThreadインスタンスを作る。
                MyThread myThread1 = new MyThread(500);
                MyThread myThread2 = new MyThread(1000);

                // delegate により ThreadStart であるMyThread.Run メソッドを
                // Threadオブジェクトに引き渡す。
                // Thread.Start()が呼ばれた時点で、
                // delegateされた MyThread.Runが呼ばれる。
                Thread thread1 = new Thread(new ThreadStart(myThread1.Run));
                Thread thread2 = new Thread(new ThreadStart(myThread2.Run));

                thread1.Start();
                thread2.Start();

                Thread.Sleep(5000);

      
                thread1.Abort();
                thread2.Abort();
      
                thread1.Join();
                thread2.Join();
      
                Console.WriteLine();
                Console.WriteLine("MyThread.Run has finished");
        }
}

4.Windows.Forms.Control における非同期プログラミング機能

注意:Windows フォーム コントロールはスレッドセーフではありません。
したがって、勝手にスレッドを作成して、その中で Windows.Formsのコントロールをアクセスするのは”危険”です。

Windows フォーム コントロールは、作成元のスレッドのみで操作できます。
Windows.Forms でボタンを押したら、重い処理をバックグラウンドで実行したいという場合があります。
この場合、重い処理をスレッドで処理することになると思いますが、重い処理のスレッドから直接コントロールの操作をする場合が該当します。

このとき、Windows.Forms のメインスレッド→重い処理のスレッド と2つのスレッドが動くことになりますが、
重い処理のスレッドから Windows.Forms のコントロールを直接触ると、 Windows.Forms のコントロールはスレッドアンセーフなので、
不都合が生じる場合があります。

このような問題を避けるために、Windows.Forms.Control では、BeginInvoke, EndInvoke という非同期呼び出しの仕組みを持っています。
これにより、任意のスレッドから、コントロールのスレッドを呼び出すことができます。

バックグラウンドスレッドからコントロールのプロパティを取得または設定する(コントロールのメソッドを呼び出す)場合は、
コントロールの作成元のスレッドに呼び出しをマーシャリングする必要がありますが、BeginInvoke, EndInvoke が
高速なマーシャリングを行ってくれるので、マーシャリングは気にすることなく呼び出せばOKです。

4.1 Windows.Forms.Control における非同期プログラミングのデザインパターン

.NETフレームワークでサポートされている非同期プログラミングでは、次の4つのパターンをサポートしています。
以下の例では、(3) だけ解説&ソースコードを載せておきます。
(1), (2), (4) のデザインパターンは、非同期デリゲートで書いていますので、同じパターンでいけると思います。

(1) BeginInvoke、EndInvoke の使用
操作がまだ完了していない場合は、その操作が完了するまでブロックします。

(2) ポーリングによる完了チェック
IAsyncResult.IsCompleted プロパティの戻り値をポーリングし、呼び出しの完了を調べます。

(3) BeginInvoke、WaitHandle、EndInvoke の使用
IAsyncResult を待機します。このオプションと直前のオプションの違いは、クライアントがタイムアウトを設定して定期的にウェイクアップできるかどうかという点にあります。

(4) コールバックの使用
非同期呼び出しの開始時にコールバック デリゲートを指定します。

参考書

(1) 非同期呼び出し (MS VisualStudio.NE のヘルプ)

4.3 Windows.Forms.Control における非同期プログラミング(BeginInvoke、WaitHandle、EndInvoke)

この例では、ボタンを押すと、重い処理( HeavyThread.Execute() ) のスレッドを実行します。
このExecute() では、重い処理の変わりにスリープしているだけです。また、乱数を発生させてプログレスバーコントロールに値をセットします。

HeavyThread.Execute() の中で、プログレスバーに値をセットする必要が生じた時点で BeginInvoke を呼び出し、
コントロールに値をセットしています。BeginInvoke で呼び出されるデリゲート(SetProgressBarValue)は、
コントロールが動作しているスレッドの中で呼び出されます。

Form1.cs

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Threading;

namespace Asyncronous1
{
    /// <summary>
    /// Form1 の概要の説明です。
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        private Thread heavyThread;
        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.ProgressBar progressBar1;
        /// <summary>
        /// 必要なデザイナ変数です。
        /// </summary>
        private System.ComponentModel.Container components = null;

        public Form1()
        {
            //
            // Windows フォーム デザイナ サポートに必要です。
            //
            InitializeComponent();

            //
            // TODO: InitializeComponent 呼び出しの後に、
            // コンストラクタ コードを追加してください。
        }

        /// <summary>
        /// 使用されているリソースに後処理を実行します。
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディタで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.progressBar1 = new System.Windows.Forms.ProgressBar();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(168, 56);
            this.button1.Name = "button1";
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // progressBar1
            // 
            this.progressBar1.Location = new System.Drawing.Point(40, 16);
            this.progressBar1.Name = "progressBar1";
            this.progressBar1.Size = new System.Drawing.Size(224, 24);
            this.progressBar1.TabIndex = 2;
            // 
            // Form1
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
            this.ClientSize = new System.Drawing.Size(292, 266);
            this.Controls.AddRange(new System.Windows.Forms.Control[] {
                this.progressBar1,
                this.button1});
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.Closed += new System.EventHandler(this.OnClosed);
            this.ResumeLayout(false);

        }
        #endregion

        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main() 
        {
            Application.Run(new Form1());
            }

        public string SetProgressBarValue(int progressBarValue)
        {
            Console.WriteLine("SetProgressBarValue: Thread.Name 
                = {0}", Thread.CurrentThread.Name);

            progressBar1.Value = progressBarValue;
            return progressBar1.Value.ToString();
        }

        public void SetButton1Enabled(bool enabled)
        {
            button1.Enabled = enabled;
        }

        private void button1_Click(object sender, System.EventArgs e)
        {
            Console.WriteLine("button1_Click: Thread.Name 
                = {0}", Thread.CurrentThread.Name);

            // 重い処理をスレッドを作って処理をする。
            AbstractExecutor hThread = new HeavyThread(this);
            heavyThread = new Thread(new ThreadStart(hThread.Execute ));
            heavyThread.IsBackground = true;
            heavyThread.Name = "HeavyThread";
            heavyThread.Start();
            button1.Enabled = false;
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            Thread.CurrentThread.Name = "MainThread";
            Console.WriteLine("Form1_Load: Thread.Name 
                = {0}", Thread.CurrentThread.Name);  
        }

        private void OnClosed(object sender, System.EventArgs e)
        {
            if (heavyThread != null && heavyThread.IsAlive)
                heavyThread.Abort();
        }
    }
}

HeavyThread.cs

using System;
using System.Threading;

namespace Asyncronous1
{
    /// <summary>
    /// HeavyThread の概要の説明です。
    /// </summary>
    abstract public class  AbstractExecutor
    {
        protected System.Windows.Forms.Control control;

        public AbstractExecutor(System.Windows.Forms.Control control)
        {
            this.control = control;
        }

        abstract public void Execute();
    }

    public class HeavyThread: AbstractExecutor
    {
        //      private Form1 form1;
        public HeavyThread(System.Windows.Forms.Control form1) : base(form1)
        {
        }

        /// <summary>
        /// デリゲートの宣言
        /// </summary>
        public delegate string MyDelegate( int myInt );
        public delegate void ButtonEnabledDelegate( bool Enabled );

        public override void Execute() 
        {
            Console.WriteLine("HeavyThreadProc: Thread.Name 
                = {0}", Thread.CurrentThread.Name);

            for (int i = 0; i < 10; i++)
            {
                // 重い処理の実行
                System.Random rnd = new System.Random();
                int result = rnd.Next(100);
                Thread.Sleep(1000);

                // 処理結果をデリゲートでコントロールに書き出す。
                try 
                {
                    // デリゲートの宣言
                    MyDelegate myDelegate
                        = new MyDelegate( ((Form1)control).SetProgressBarValue );
                    // BeginInvoke でデリゲートに渡すパラメータ
                    object [] args = { result };

                    // デリゲートを BeginInvoke で起動する。
                    IAsyncResult ar = control.BeginInvoke( myDelegate, args );

                    // シグナルを待つ。
                    ar.AsyncWaitHandle.WaitOne();
                    if (ar.IsCompleted)
                    {
                        Console.WriteLine("Completed");
                        // EndInvoke で結果を得る。
                        object obj = control.EndInvoke(ar);
                    }
                }

                catch (ThreadInterruptedException e) 
                {
                }
            }
            // デリゲートの宣言
            ButtonEnabledDelegate buttonDelegate 
                = new ButtonEnabledDelegate( ((Form1)control).SetButton1Enabled );
            // BeginInvoke でデリゲートに渡すパラメータ
            object [] args2 = { true };
            
            // デリゲートを BeginInvoke で起動する。
            control.BeginInvoke( buttonDelegate, args2 );
        }
    }
}

5.非同期デリゲートによるプログラミング

この非同期デリゲートによるプログラミングは、スレッド、Windows.Forms.Control による非同期プログラミングとは本質的に異なる点があります。
それは、コンパイラー、および CLR (Common Langage Runtime) によりサポートされている点です。
スレッド、Windows.Forms.Control による非同期プログラミング は、.インテリセンスによるヘルプが効くので、インテリセンスを使ってお気軽に書くことができます。
しかし、デリゲートによる非同期プログラミングは、デリゲートのシグネチャをコンパイラーが判断して、BeginInvoke, EndInvoke などのメソッドを追加します。
このため、インテリセンスによるヘルプが効きません。
この点、これまでの .NET プログラミングに比べて異色です。このことを理解しておく必要があります。
このため、テンプレートがないと、1からヘルプを見ながら書き起こすのは不可能に近いでしょう。

5.1 非同期デリゲート のデザインパターン

.NETフレームワークでサポートされている非同期プログラミングでは、次の4つのパターンをサポートしています。

(1) BeginInvoke、EndInvoke の使用
操作がまだ完了していない場合は、その操作が完了するまでブロックします。

(2) ポーリングによる完了チェック
IAsyncResult.IsCompleted プロパティの戻り値をポーリングし、呼び出しの完了を調べます。

(3) BeginInvoke、WaitHandle、EndInvoke の使用
IAsyncResult を待機します。このオプションと直前のオプションの違いは、クライアントがタイムアウトを設定して定期的にウェイクアップできるかどうかという点にあります。

(4) コールバックの使用
非同期呼び出しの開始時にコールバック デリゲートを指定します。

5.2 参考書

(1) 非同期デリゲート (MS VisualStudio.NE のヘルプ)

5.3 非同期デリゲート(コールバック)

以下では、ソースコードをブロックごとに説明します。

ボタンが押されたら、非同期デリゲートを呼び出す。

1. 非同期デリゲートのインスタンスを作る。
非同期メソッド呼び出しをしたい任意のメソッド(ここでは AsyncCall) を非同期デリゲートを作る。
このために、MyDelegate というデリゲートを宣言する必要があります。

2. 非同期デリゲートに渡すパラメータを決める。
引数も任意のパラメータを取ることができます。ここでは、param1, param2, ref param3 の3つを引数にとり、param3 = param1*param2 を計算することにします。
非同期メソッドから結果を受け取るための参照渡しなども可能。ただし、この場合 BeginInvoke, EndInvoke のパラメータとしてあわせておく必要がある。

3. 次に、非同期呼び出しが完了した時点で呼び出されるコールバック関数を用意します。
ここでは、非同期呼び出しが完了した時点で MyAsyncCallback を呼び出すようにします。
コールバック関数を使用しない方法もあります。

4. 非同期デリゲート.BeginInvoke() で非同期デリゲートを呼び出します。
ここで、パラメータは、param1, param2, ..... param-n, AsyncCallback, AsyncState と、デリゲートのパラメータに続き、AsyncCallback, AsyncState を
引数に渡す必要があります。
AsyncState は、非同期の状態を渡すために使うようですが、利用価値がわかりません。ここでは、null で問題ありません。

private void button1_Click(object sender, System.EventArgs e)
{
    // 非同期で呼び出すメソッド(AsyncronousCall) のデリゲートを作ります。
    asyncCall = new MyDelegate(this.AsyncronousCall);

    // 適当なパラメータを渡します。
    int param1 = 123;
    int param2 = 3;
    int param3 = 0;

    // 非同期呼び出しが完了した時点で呼び出されるコールバック関数です。
    System.AsyncCallback ac = new System.AsyncCallback( MyAsyncCallback );
                        
    // デリゲートの BeginInvoke で非同期メソッドを呼び出すことができます。
    // デリゲートに対して BeginInvoke が呼び出せますが、
    // これはコンパイラがサポートしている機能です。
    IAsyncResult ar = asyncCall.BeginInvoke(param1, param2, ref param3, ac, null);
}

非同期デリゲート宣言

非同期デリゲートのためのデリゲート宣言が必要です。
この例では、int param1, int param2, ref int param3 を引数として渡し、bool の返り値を得ます。
また、非同期メソッドの中で、param3 = param1*param2 として、param3 を参照渡しにして計算結果を返すようにします。
このパラメータは任意です。

    /// <summary>
    /// BeginInvoke に非同期メソッドを渡すためにデリゲート宣言が必要です。
    /// </summary>
    public delegate bool MyDelegate(
        int param1, 
        int param2,
        ref int param3);

非同期メソッド

非同期で実行されるメソッドの本体です。
通常は、重い計算など時間のかかる処理を非同期メソッド呼び出しします。
ここでは、単に param3 = param1 * param2 の計算をしているだけなので、ダミーで1秒スリープを5回呼んでいます。

注意: 非同期デリゲートでは、スレッドプールが勝手に適用されます。
スレッドプールのデフォルトのワーカースレッドの最大値は25個です。
一応、それを確かめるために、ThreadPool.GetAvailableThreads() でスレッドプールの状態をコンソールに書き出しています

 MyDelegate asyncCall;

 /// <summary>
 /// AsyncronousCall は、非同期メソッドです。
 /// このメソッドが BeginInvoke により非同期呼び出しされます。
 /// BeginInvoke でこのメソッドを渡す必要があるので、デリゲート宣言が必要です。
 /// パラメータは任意です。
 /// </summary>
 /// <param name="param1"></param>
 /// <param name="param2"></param>
 /// <param name="param3"></param>
 /// <returns></returns>
 public bool AsyncronousCall(int param1, int param2, ref int param3)
 {
     Console.WriteLine("AsyncronousCall");

     // この非同期呼び出しは、スレッドプールに作られる。
     // そこで、GetAvailableThreadsメソッドによりスレッドプールを調べる。
     // workerThreads: スレッドプールに追加可能なワーカー スレッドの数
     // competionPortThreads: 非同期 I/O スレッドの数
     int workerThreads, competionPortThreads;
     System.Threading.ThreadPool.GetAvailableThreads
        (out workerThreads, out competionPortThreads);
     Console.WriteLine("AsyncronousCall: workerThreads:{0}, 
         competionPortThreads:{1}", workerThreads, competionPortThreads);

     // 重い処理
     for(int i = 0; i < 5; i++)
     {
         Console.WriteLine(i);
         System.Threading.Thread.Sleep(1000);
     }

     param3 = param1 * param2;
     return true;
 }

非同期メソッドが完了したときに呼ばれるコールバック 非同期メソッドが完了すると、BeginInvoke の引数で渡した非同期メソッドがコールバックされます。
非同期メソッドの結果を取得するには、非同期デリゲート.EndInvoke() 呼び出しが必要です。
このコールバックの引数には IAsyncResult がわたってきます。この IAsyncResult を使って、
非同期デリゲートを得ることができます。
それが、MyDelegate asyncCall = ..... です。
ちょっとわかりにくいですが、このようにすることにより呼び出し側の非同期デリゲートに依存せずに書くことができます。
IAsyncResult から得た非同期デリゲート(ここでは、asyncCall )の EndInvoke メソッドを呼び出します。
引数は、非同期デリゲートで使用したパラメータのうち値のかえる in/out, out, ref のすべてと、引数で渡される IAsyncResult になります。
また、非同期メソッドの返り値が、EndInvoke の返り値として返されます。

注意: 非同期デリゲートを宣言しただけで、BeginInvoke, EndInvoke などがコンパイラーにより提供されます。

/// <summary>
/// MyAsyncCallback は、非同期メソッドが完了したときに呼び出される
/// コールバックメソッドです。
/// </summary>
/// <param name="ar"></param>
public void MyAsyncCallback(IAsyncResult ar)
{
                        
    // EndInvoke で結果を取り出す。
    // しかし、EndInvoke は非同期メソッドのデリゲートに対してサポートされているので、
    // 非同期メソッドのデリゲートを知る必要がある。
    // そのために、System.Runtime.Remoting.Messaging.AsyncResult.AsyncDelegate 
    // プロパティから非同期メソッドのデリゲートを取得します。
    MyDelegate asyncCall = (MyDelegate)
        ((System.Runtime.Remoting.Messaging.AsyncResult)ar).AsyncDelegate;

    // 非同期メソッドのデリゲート asyncCall より、EndInvoke をコールすることにより、
    // パラメータ、返り値を得ることができる。
    int param3 = 0;
    bool result = asyncCall.EndInvoke(ref param3, ar);
    
    Console.WriteLine("MyAsyncCallback: param3: {0}, bool: {1}", param3, result);
}

5.4 非同期デリゲート(コールバック)のソースコード

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace Asyncronous2
{
    /// <summary>
    /// Form1 の概要の説明です。
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Button button1;
        /// <summary>
        /// 必要なデザイナ変数です。
        /// </summary>
        private System.ComponentModel.Container components = null;

        public Form1()
        {
            //
            // Windows フォーム デザイナ サポートに必要です。
            //
            InitializeComponent();

            //
            // TODO: InitializeComponent 呼び出しの後に、
            // コンストラクタ コードを追加してください。
        }

        /// <summary>
        /// 使用されているリソースに後処理を実行します。
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディタで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            //
            // button1
            //
            this.button1.Location = new System.Drawing.Point(168, 80);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(72, 56);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.Click += new System.EventHandler(this.button1_Click);
            //
            // Form1
            //
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
            this.ClientSize = new System.Drawing.Size(292, 266);
            this.Controls.AddRange(new System.Windows.Forms.Control[] {
                this.button1});
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
        }
        #endregion

        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.Run(new Form1());
        }

        // 
        private void button1_Click(object sender, System.EventArgs e)
        {
            // 非同期で呼び出すメソッド(AsyncronousCall) のデリゲートを作ります。
            asyncCall = new MyDelegate(this.AsyncronousCall);

            // 適当なパラメータを渡します。
            int param1 = 123;
            int param2 = 3;
            int param3 = 0;

            // 非同期呼び出しが完了した時点で呼び出されるコールバック関数です。
            System.AsyncCallback ac = new System.AsyncCallback( MyAsyncCallback );
                       
            // デリゲートの BeginInvoke で非同期メソッドを呼び出すことができます。
            // デリゲートに対して BeginInvoke が呼び出せますが、
            // これはコンパイラがサポートしている機能です。
            IAsyncResult ar
                = asyncCall.BeginInvoke(param1, param2, ref param3, ac, null);
        }

        /// <summary>
        /// BeginInvoke に非同期メソッドを渡すためにデリゲート宣言が必要です。
        /// </summary>
        public delegate bool MyDelegate(
            int param1,
            int param2,
            ref int param3);

        MyDelegate asyncCall;

        /// <summary>
        /// AsyncronousCall は、非同期メソッドです。
        /// このメソッドが BeginInvoke により非同期呼び出しされます。
        /// BeginInvoke でこのメソッドを渡す必要があるので、デリゲート宣言が必要です。
        /// パラメータは任意です。
        /// </summary>
        /// <param name="param1"></param>
        /// <param name="param2"></param>
        /// <param name="param3"></param>
        /// <returns></returns>
        public bool AsyncronousCall(int param1, int param2, ref int param3)
        {
            Console.WriteLine("AsyncronousCall");

            // この非同期呼び出しは、スレッドプールに作られる。
            // そこで、GetAvailableThreadsメソッドによりスレッドプールを調べる。
            // workerThreads: スレッドプールに追加可能なワーカー スレッドの数
            // competionPortThreads: 非同期 I/O スレッドの数
            int workerThreads, competionPortThreads;
            System.Threading.ThreadPool.GetAvailableThreads
                (out workerThreads, out competionPortThreads);
            Console.WriteLine("AsyncronousCall: workerThreads:{0},
                competionPortThreads:{1}", workerThreads, competionPortThreads);

            // 重い処理
            for(int i = 0; i < 5; i++)
            {
                Console.WriteLine(i);
                System.Threading.Thread.Sleep(1000);
            }

            param3 = param1 * param2;

            return true;
        }
               
        /// <summary>
        /// MyAsyncCallback は、非同期メソッドが完了したときに呼び出される
        /// コールバックメソッドです。
        /// </summary>
        /// <param name="ar"></param>
        public void MyAsyncCallback(IAsyncResult ar)
        {
                       
            // EndInvoke で結果を取り出す。
            // しかし、EndInvoke は非同期メソッドのデリゲートに対してサポートされているので、
            // 非同期メソッドのデリゲートを知る必要がある。
            // そのために、System.Runtime.Remoting.Messaging.AsyncResult.AsyncDelegate
            // プロパティから非同期メソッドのデリゲートを取得します。
            MyDelegate asyncCall = (MyDelegate)
                ((System.Runtime.Remoting.Messaging.AsyncResult)ar).AsyncDelegate;

            // 非同期メソッドのデリゲート asyncCall より、EndInvoke をコールすることにより、
            // パラメータ、返り値を得ることができる。
            int param3 = 0;
            bool result = asyncCall.EndInvoke(ref param3, ar);
                       
            Console.WriteLine("MyAsyncCallback:
                param3: {0}, bool: {1}", param3, result);
        }
    }
}

5.5 非同期デリゲート(BeginInvoke, EndInvoke)

以下では、ソースコードをブロックごとに説明します。

ボタンが押されたら、非同期デリゲートを呼び出す。

1. 非同期デリゲートのインスタンスを作る。
非同期メソッド呼び出しをしたい任意のメソッド(ここでは AsyncCall) を非同期デリゲートを作る。
このために、MyDelegate というデリゲートを宣言する必要があります。

2. 非同期デリゲートに渡すパラメータを決める。
引数も任意のパラメータを取ることができます。ここでは、param1, param2, ref param3 の3つを引数にとり、param3 = param1*param2 を計算することにします。
非同期メソッドから結果を受け取るための参照渡しなども可能。ただし、この場合 BeginInvoke, EndInvoke のパラメータとしてあわせておく必要がある。

3. 次に、非同期呼び出しが完了した時点で呼び出されるコールバック関数を用意します。
ここでは、非同期呼び出しが完了した時点で MyAsyncCallback を呼び出すようにします。
コールバック関数を使用しない方法もあります。

4. 非同期デリゲート.BeginInvoke() で非同期デリゲートを呼び出します。
ここで、パラメータは、param1, param2, ..... param-n, AsyncCallback, AsyncState と、デリゲートのパラメータに続き、AsyncCallback, AsyncState を
引数に渡す必要がありますが、コールバックしないので、どちらも null, null にします。

5. EndInvoke メソッドにより、非同期デリゲートが完了するまで実行がブロックされます。

private void button1_Click(object sender, System.EventArgs e)
{
    // 非同期で呼び出すメソッド(AsyncronousCall) の
    // デリゲイトを作ります。
    asyncCall = new MyDelegate(this.AsyncronousCall);

    // 適当なパラメータを渡します。
    int param1 = 123;
    int param2 = 3;
    int param3 = 0;
                       
    // デリゲイトの BeginInvoke で非同期メソッドを
    // 呼び出すことができます。
    // デリゲイトに対して BeginInvoke が呼び出せますが、
    // これはコンパイラがサポートしている機能です。
    IAsyncResult ar
        = asyncCall.BeginInvoke
        (param1, param2, ref param3, null, null);

    // EndInvoke により、デリゲートの完了までブロックされる。
    bool result = asyncCall.EndInvoke(ref param3, ar);
                       
    Console.WriteLine("button1_Click: param3: {0},
        bool: {1}", param3, result);
}

非同期デリゲート宣言

非同期デリゲートのためのデリゲート宣言が必要です。
この例では、int param1, int param2, ref int param3 を引数として渡し、bool の返り値を得ます。
また、非同期メソッドの中で、param3 = param1*param2 として、param3 を参照渡しにして計算結果を返すようにします。
このパラメータは任意です。

/// <summary>
/// BeginInvoke に非同期メソッドを渡すためにデリゲート宣言が必要です。
/// </summary>
public delegate bool MyDelegate(
    int param1,
    int param2,
    ref int param3);

非同期メソッド

非同期で実行されるメソッドの本体です。
通常は、重い計算など時間のかかる処理を非同期メソッド呼び出しします。
ここでは、単に param3 = param1 * param2 の計算をしているだけなので、ダミーで1秒スリープを5回呼んでいます。

注意: 非同期デリゲートでは、スレッドプールが勝手に適用されます。
スレッドプールのデフォルトのワーカースレッドの最大値は25個です。
一応、それを確かめるために、ThreadPool.GetAvailableThreads() でスレッドプールの状態をコンソールに書き出しています

MyDelegate asyncCall;

/// <summary>
/// AsyncronousCall は、非同期メソッドです。
/// このメソッドが BeginInvoke により非同期呼び出しされます。
/// BeginInvoke でこのメソッドを渡す必要があるので、
/// デリゲート宣言が必要です。パラメータは任意です。
/// </summary>
/// <param name="param1"></param>
/// <param name="param2"></param>
/// <param name="param3"></param>
/// <returns></returns>
public bool AsyncronousCall(int param1, int param2, ref int param3)
{
    Console.WriteLine("AsyncronousCall");

    // この非同期呼び出しは、スレッドプールに作られる。
    // そこで、GetAvailableThreadsメソッドによりスレッドプールを調べる。
    // workerThreads: スレッドプールに追加可能なワーカー スレッドの数
    // competionPortThreads: 非同期 I/O スレッドの数
    int workerThreads, competionPortThreads;
    System.Threading.ThreadPool.GetAvailableThreads
        (out workerThreads, out competionPortThreads);
    Console.WriteLine("AsyncronousCall: workerThreads:{0},
        competionPortThreads:{1}",
        workerThreads, competionPortThreads);

    // 重い処理
    for(int i = 0; i < 5; i++)
    {
        Console.WriteLine(i);
        System.Threading.Thread.Sleep(1000);
    }
    param3 = param1 * param2;

    return true;
}

5.6 非同期デリゲート(BeginInvoke, EndInvoke)のソースコード

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace Asyncronous3
{
    /// <summary>
    /// Form1 の概要の説明です。
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Button button1;
        /// <summary>
        /// 必要なデザイナ変数です。
        /// </summary>
        private System.ComponentModel.Container components = null;

        public Form1()
        {
            //
            // Windows フォーム デザイナ サポートに必要です。
            //
            InitializeComponent();

            //
            // TODO: InitializeComponent 呼び出しの後に、
            // コンストラクタ コードを追加してください。
        }

        /// <summary>
        /// 使用されているリソースに後処理を実行します。
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディタで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(168, 80);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(72, 56);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.Click 
                += new System.EventHandler(this.button1_Click);
            // 
            // Form1
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
            this.ClientSize = new System.Drawing.Size(292, 266);
            this.Controls.AddRange(new System.Windows.Forms.Control[] {
                this.button1});
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
        }
        #endregion

        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main() 
        {
            Application.Run(new Form1());
        }

        // 
        private void button1_Click(object sender, System.EventArgs e)
        {
            // 非同期で呼び出すメソッド(AsyncronousCall) の
            // デリゲイトを作ります。
            asyncCall = new MyDelegate(this.AsyncronousCall);

            // 適当なパラメータを渡します。
            int param1 = 123;
            int param2 = 3;
            int param3 = 0;
                        
            // デリゲイトの BeginInvoke で非同期メソッドを
            // 呼び出すことができます。
            // デリゲイトに対して BeginInvoke が呼び出せますが、
            // これはコンパイラがサポートしている機能です。
            IAsyncResult ar = asyncCall.BeginInvoke
                (param1, param2, ref param3, null, null);

            // EndInvoke により、デリゲートの完了までブロックされる。
            bool result = asyncCall.EndInvoke(ref param3, ar);
                        
            Console.WriteLine("button1_Click: param3: {0}, 
                bool: {1}", param3, result);
        }

        /// <summary>
        /// BeginInvoke に非同期メソッドを渡すためにデリゲイト宣言が必要です。
        /// </summary>
        public delegate bool MyDelegate(
            int param1, 
            int param2,
            ref int param3);

        MyDelegate asyncCall;

        /// <summary>
        /// AsyncronousCall は、非同期メソッドです。
        /// このメソッドが BeginInvoke により非同期呼び出しされます。
        /// BeginInvoke でこのメソッドを渡す必要があるので、
        /// デリゲイト宣言が必要です。パラメータは任意です。
        /// </summary>
        /// <param name="param1"></param>
        /// <param name="param2"></param>
        /// <param name="param3"></param>
        /// <returns></returns>
        public bool AsyncronousCall(int param1, 
            int param2, ref int param3)
        {
            Console.WriteLine("AsyncronousCall");

            // この非同期呼び出しは、スレッドプールに作られる。
            // そこで、GetAvailableThreadsメソッドにより
            // スレッドプールを調べる。
            // workerThreads: スレッドプールに追加可能なワーカー スレッドの数
            // competionPortThreads: 非同期 I/O スレッドの数
            int workerThreads, competionPortThreads;
            System.Threading.ThreadPool.GetAvailableThreads
                (out workerThreads, out competionPortThreads);
            Console.WriteLine("AsyncronousCall: workerThreads:{0}, 
                competionPortThreads:{1}", 
                workerThreads, competionPortThreads);

            // 重い処理
            for(int i = 0; i < 5; i++)
            {
                Console.WriteLine(i);
                System.Threading.Thread.Sleep(1000);
            }

            param3 = param1 * param2;
            return true;
        }
    }
}

5.7 非同期デリゲート(ポーリング)

以下では、ソースコードをブロックごとに説明します。 

ボタンが押されたら、非同期デリゲートを呼び出す。

1. 非同期デリゲートのインスタンスを作る。
非同期メソッド呼び出しをしたい任意のメソッド(ここでは AsyncCall) を非同期デリゲートを作る。
このために、MyDelegate というデリゲートを宣言する必要があります。

2. 非同期デリゲートに渡すパラメータを決める。
引数も任意のパラメータを取ることができます。ここでは、param1, param2, ref param3 の3つを引数にとり、param3 = param1*param2 を計算することにします。
非同期メソッドから結果を受け取るための参照渡しなども可能。ただし、この場合 BeginInvoke, EndInvoke のパラメータとしてあわせておく必要がある。

3. 次に、非同期呼び出しが完了した時点で呼び出されるコールバック関数を用意します。
ここでは、非同期呼び出しが完了した時点で MyAsyncCallback を呼び出すようにします。
コールバック関数を使用しない方法もあります。

4. 非同期デリゲート.BeginInvoke() で非同期デリゲートを呼び出します。
ここで、パラメータは、param1, param2, ..... param-n, AsyncCallback, AsyncState と、デリゲートのパラメータに続き、AsyncCallback, AsyncState を
引数に渡す必要がありますが、コールバックしないので、どちらも null, null にします。

5. ポーリングは、ar.IsCompleted により非同期デリゲートの状態を監視します。

6. ポーリングで非同期デリゲートが完了したことが確認できたらば、EndInvoke メソッドで非同期デリゲートの結果を取り出します。

private void button1_Click(object sender, System.EventArgs e)
{
    // 非同期で呼び出すメソッド(AsyncronousCall) のデリゲイトを作ります。
    asyncCall = new MyDelegate(this.AsyncronousCall);

    // 適当なパラメータを渡します。
    int param1 = 123;
    int param2 = 3;
    int param3 = 0;
                       
    // デリゲイトの BeginInvoke で非同期メソッドを呼び出すことができます。
    // デリゲイトに対して BeginInvoke が呼び出せますが、
    // これはコンパイラがサポートしている機能です。
    IAsyncResult ar = asyncCall.BeginInvoke
        (param1, param2, ref param3, null, null);

    // ar.InCompleted で、デリゲートが完了したかどうか、ポーリングする。
    while( ! ar.IsCompleted )
    {
        Console.WriteLine("Polling");
        System.Threading.Thread.Sleep(1000);
    }

    // 完了したので EndInvoke で結果を得る。
    bool result = asyncCall.EndInvoke(ref param3, ar);
                       
    Console.WriteLine("button1_Click: param3: {0}, bool: {1}", param3, result);
    }

非同期デリゲート宣言

非同期デリゲートのためのデリゲート宣言が必要です。
この例では、int param1, int param2, ref int param3 を引数として渡し、bool の返り値を得ます。
また、非同期メソッドの中で、param3 = param1*param2 として、param3 を参照渡しにして計算結果を返すようにします。
このパラメータは任意です。

/// <summary>
/// BeginInvoke に非同期メソッドを渡すためにデリゲート宣言が必要です。
/// </summary>
public delegate bool MyDelegate(
    int param1,
    int param2,
    ref int param3);

非同期メソッド

非同期で実行されるメソッドの本体です。
通常は、重い計算など時間のかかる処理を非同期メソッド呼び出しします。
ここでは、単に param3 = param1 * param2 の計算をしているだけなので、ダミーで1秒スリープを5回呼んでいます。

注意: 非同期デリゲートでは、スレッドプールが勝手に適用されます。
スレッドプールのデフォルトのワーカースレッドの最大値は25個です。
一応、それを確かめるために、ThreadPool.GetAvailableThreads() でスレッドプールの状態をコンソールに書き出しています

MyDelegate asyncCall;

/// <summary>
/// AsyncronousCall は、非同期メソッドです。
/// このメソッドが BeginInvoke により非同期呼び出しされます。
/// BeginInvoke でこのメソッドを渡す必要があるので、デリゲート宣言が必要です。
/// パラメータは任意です。
/// </summary>
/// <param name="param1"></param>
/// <param name="param2"></param>
/// <param name="param3"></param>
/// <returns></returns>
public bool AsyncronousCall(int param1, int param2, ref int param3)
{
    Console.WriteLine("AsyncronousCall");

    // この非同期呼び出しは、スレッドプールに作られる。
    // そこで、GetAvailableThreadsメソッドによりスレッドプールを調べる。
    // workerThreads: スレッドプールに追加可能なワーカー スレッドの数
    // competionPortThreads: 非同期 I/O スレッドの数
    int workerThreads, competionPortThreads;
    System.Threading.ThreadPool.GetAvailableThreads
        (out workerThreads, out competionPortThreads);
    Console.WriteLine("AsyncronousCall: workerThreads:{0},
        competionPortThreads:{1}", workerThreads, competionPortThreads);

    // 重い処理
    for(int i = 0; i < 5; i++)
    {
        Console.WriteLine(i);
        System.Threading.Thread.Sleep(1000);
        }
    param3 = param1 * param2;
    return true;
} 

5.8 非同期デリゲート(ポーリング)のソースコード

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace Asyncronous3
{
    /// <summary>
    /// Form1 の概要の説明です。
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Button button1;
        /// <summary>
        /// 必要なデザイナ変数です。
        /// </summary>
        private System.ComponentModel.Container components = null;

        public Form1()
        {
            //
            // Windows フォーム デザイナ サポートに必要です。
            //
            InitializeComponent();

            //
            // TODO: InitializeComponent 呼び出しの後に、
            // コンストラクタ コードを追加してください。
        }

        /// <summary>
        /// 使用されているリソースに後処理を実行します。
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディタで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
        this.button1 = new System.Windows.Forms.Button();
        this.SuspendLayout();
        // 
        // button1
        // 
        this.button1.Location = new System.Drawing.Point(168, 80);
        this.button1.Name = "button1";
        this.button1.Size = new System.Drawing.Size(72, 56);
        this.button1.TabIndex = 0;
        this.button1.Text = "button1";
        this.button1.Click += new System.EventHandler(this.button1_Click);
        // 
        // Form1
        // 
        this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
        this.ClientSize = new System.Drawing.Size(292, 266);
        this.Controls.AddRange(new System.Windows.Forms.Control[] {
            this.button1});
        this.Name = "Form1";
        this.Text = "Form1";
        this.ResumeLayout(false);

        }
        #endregion

        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main() 
        {
            Application.Run(new Form1());
        }

        // 
        private void button1_Click(object sender, System.EventArgs e)
        {
            // 非同期で呼び出すメソッド(AsyncronousCall) のデリゲイトを作ります。
            asyncCall = new MyDelegate(this.AsyncronousCall);

            // 適当なパラメータを渡します。
            int param1 = 123;
            int param2 = 3;
            int param3 = 0;
                        
            // デリゲイトの BeginInvoke で非同期メソッドを呼び出すことができます。
            // デリゲイトに対して BeginInvoke が呼び出せますが、
            // これはコンパイラがサポートしている機能です。
            IAsyncResult ar = asyncCall.BeginInvoke(param1, param2, ref param3, null, null);

            // ar.InCompleted で、デリゲートが完了したかどうか、ポーリングする。
            while( ! ar.IsCompleted )
            {
                Console.WriteLine("Polling");
                System.Threading.Thread.Sleep(1000);
            }

            // 完了したので EndInvoke で結果を得る。
            bool result = asyncCall.EndInvoke(ref param3, ar);
                        
            Console.WriteLine("button1_Click: param3: {0}, bool: {1}", param3, result);
        }

        /// <summary>
        /// BeginInvoke に非同期メソッドを渡すためにデリゲイト宣言が必要です。
        /// </summary>
        public delegate bool MyDelegate(
            int param1, 
            int param2,
            ref int param3);

        MyDelegate asyncCall;

        /// <summary>
        /// AsyncronousCall は、非同期メソッドです。
        /// このメソッドが BeginInvoke により非同期呼び出しされます。
        /// BeginInvoke でこのメソッドを渡す必要があるので、デリゲイト宣言が必要です。
        /// パラメータは任意です。
        /// </summary>
        /// <param name="param1"></param>
        /// <param name="param2"></param>
        /// <param name="param3"></param>
        /// <returns></returns>
        public bool AsyncronousCall(int param1, int param2, ref int param3)
        {
            Console.WriteLine("AsyncronousCall");

            // この非同期呼び出しは、スレッドプールに作られる。
            // そこで、GetAvailableThreadsメソッドによりスレッドプールを調べる。
            // workerThreads: スレッドプールに追加可能なワーカー スレッドの数
            // competionPortThreads: 非同期 I/O スレッドの数
            int workerThreads, competionPortThreads;
            System.Threading.ThreadPool.GetAvailableThreads
                (out workerThreads, out competionPortThreads);
            Console.WriteLine("AsyncronousCall: workerThreads:{0}, competionPortThreads:{1}", 
                workerThreads, competionPortThreads);

            // 重い処理
            for(int i = 0; i < 5; i++)
            {
                Console.WriteLine(i);
                System.Threading.Thread.Sleep(1000);
            }
            param3 = param1 * param2;
            return true;
        }
    }
}

5.9 非同期デリゲート(BeginInvoke、WaitHandle、EndInvoke の使用)

以下では、ソースコードをブロックごとに説明します。

ボタンが押されたら、非同期デリゲートを呼び出す。

1. 非同期デリゲートのインスタンスを作る。
非同期メソッド呼び出しをしたい任意のメソッド(ここでは AsyncCall) を非同期デリゲートを作る。
このために、MyDelegate というデリゲートを宣言する必要があります。

2. 非同期デリゲートに渡すパラメータを決める。
引数も任意のパラメータを取ることができます。ここでは、param1, param2, ref param3 の3つを引数にとり、param3 = param1*param2 を計算することにします。
非同期メソッドから結果を受け取るための参照渡しなども可能。ただし、この場合 BeginInvoke, EndInvoke のパラメータとしてあわせておく必要がある。

3. 次に、非同期呼び出しが完了した時点で呼び出されるコールバック関数を用意します。
ここでは、非同期呼び出しが完了した時点で MyAsyncCallback を呼び出すようにします。
コールバック関数を使用しない方法もあります。

4. 非同期デリゲート.BeginInvoke() で非同期デリゲートを呼び出します。
ここで、パラメータは、param1, param2, ..... param-n, AsyncCallback, AsyncState と、デリゲートのパラメータに続き、AsyncCallback, AsyncState を
引数に渡す必要がありますが、コールバックしないので、どちらも null, null にします。

5. ポーリングは、ar.IsCompleted により非同期デリゲートの状態を監視します。

6. ポーリングで非同期デリゲートが完了したことが確認できたらば、EndInvoke メソッドで非同期デリゲートの結果を取り出します。

    private void button1_Click(object sender, System.EventArgs e)
    {
        // 非同期で呼び出すメソッド(AsyncronousCall) のデリゲイトを作ります。
        asyncCall = new MyDelegate(this.AsyncronousCall);

        // 適当なパラメータを渡します。
        int param1 = 123;
        int param2 = 3;
        int param3 = 0;
                        
        // デリゲイトの BeginInvoke で非同期メソッドを呼び出すことができます。
        // デリゲイトに対して BeginInvoke が呼び出せますが、
        // これはコンパイラがサポートしている機能です。
        IAsyncResult ar = asyncCall.BeginInvoke
            (param1, param2, ref param3, null, null);

        // 完全にブロックせずに、タイムアウトにより処理を割り込める。
        ar.AsyncWaitHandle.WaitOne(2000, false);
        Console.WriteLine("タイムアウト発生: 2秒経過!");

        // 完全にブロックせずに、タイムアウトにより処理を割り込める。
        ar.AsyncWaitHandle.WaitOne(2000, false);
        Console.WriteLine("タイムアウト発生: さらに2秒経過!");

        bool result = asyncCall.EndInvoke(ref param3, ar);
                        
        Console.WriteLine("button1_Click: param3: {0}, bool: {1}", param3, result);
    }

非同期デリゲート宣言

非同期デリゲートのためのデリゲート宣言が必要です。
この例では、int param1, int param2, ref int param3 を引数として渡し、bool の返り値を得ます。
また、非同期メソッドの中で、param3 = param1*param2 として、param3 を参照渡しにして計算結果を返すようにします。
このパラメータは任意です。

/// <summary>
/// BeginInvoke に非同期メソッドを渡すためにデリゲート宣言が必要です。
/// </summary>
public delegate bool MyDelegate(
    int param1, 
    int param2,
    ref int param3);

非同期メソッド

非同期で実行されるメソッドの本体です。
通常は、重い計算など時間のかかる処理を非同期メソッド呼び出しします。
ここでは、単に param3 = param1 * param2 の計算をしているだけなので、ダミーで1秒スリープを5回呼んでいます。

注意: 非同期デリゲートでは、スレッドプールが勝手に適用されます。
スレッドプールのデフォルトのワーカースレッドの最大値は25個です。
一応、それを確かめるために、ThreadPool.GetAvailableThreads() でスレッドプールの状態をコンソールに書き出しています。

MyDelegate asyncCall;

/// <summary>
/// AsyncronousCall は、非同期メソッドです。
/// このメソッドが BeginInvoke により非同期呼び出しされます。
/// BeginInvoke でこのメソッドを渡す必要があるので、デリゲート宣言が必要です。
/// パラメータは任意です。
/// </summary>
/// <param name="param1"></param>
/// <param name="param2"></param>
/// <param name="param3"></param>
/// <returns></returns>
public bool AsyncronousCall(int param1, int param2, ref int param3)
{
    Console.WriteLine("AsyncronousCall");

    // この非同期呼び出しは、スレッドプールに作られる。
    // そこで、GetAvailableThreadsメソッドによりスレッドプールを調べる。
    // workerThreads: スレッドプールに追加可能なワーカー スレッドの数
    // competionPortThreads: 非同期 I/O スレッドの数
    int workerThreads, competionPortThreads;
    System.Threading.ThreadPool.GetAvailableThreads
        (out workerThreads, out competionPortThreads);
    Console.WriteLine("AsyncronousCall: workerThreads:{0}, 
        competionPortThreads:{1}", workerThreads, competionPortThreads);

    // 重い処理
    for(int i = 0; i < 5; i++)
    {
        Console.WriteLine(i);
        System.Threading.Thread.Sleep(1000);
    }

    param3 = param1 * param2;
    return true;
}

5.10 非同期デリゲート(BeginInvoke、WaitHandle、EndInvoke の使用)のソースコード

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace Asyncronous4
{
    /// <summary>
    /// Form1 の概要の説明です。
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Button button1;
        /// <summary>
        /// 必要なデザイナ変数です。
        /// </summary>
        private System.ComponentModel.Container components = null;

        public Form1()
        {
            //
            // Windows フォーム デザイナ サポートに必要です。
            //
            InitializeComponent();

            //
            // TODO: InitializeComponent 呼び出しの後に、
            // コンストラクタ コードを追加してください。
        }

        /// <summary>
        /// 使用されているリソースに後処理を実行します。
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディタで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(168, 80);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(72, 56);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // Form1
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
            this.ClientSize = new System.Drawing.Size(292, 266);
            this.Controls.AddRange(new System.Windows.Forms.Control[] {
                this.button1});
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);

        }
        #endregion

        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main() 
        {
            Application.Run(new Form1());
        }

        // 
        private void button1_Click(object sender, System.EventArgs e)
        {
            // 非同期で呼び出すメソッド(AsyncronousCall) のデリゲイトを作ります。
            asyncCall = new MyDelegate(this.AsyncronousCall);

            // 適当なパラメータを渡します。
            int param1 = 123;
            int param2 = 3;
            int param3 = 0;
                        
            // デリゲイトの BeginInvoke で非同期メソッドを呼び出すことができます。
            // デリゲイトに対して BeginInvoke が呼び出せますが、
            // これはコンパイラがサポートしている機能です。
            IAsyncResult ar = asyncCall.BeginInvoke(param1, param2, ref param3, null, null);

            // 完全にブロックせずに、タイムアウトにより処理を割り込める。
            ar.AsyncWaitHandle.WaitOne(2000, false);
            Console.WriteLine("タイムアウト発生: 2秒経過!");

            // 完全にブロックせずに、タイムアウトにより処理を割り込める。
            ar.AsyncWaitHandle.WaitOne(2000, false);
            Console.WriteLine("タイムアウト発生: さらに2秒経過!");

            bool result = asyncCall.EndInvoke(ref param3, ar);
                        
            Console.WriteLine("button1_Click: param3: {0}, bool: {1}", param3, result);
        }

        /// <summary>
        /// BeginInvoke に非同期メソッドを渡すためにデリゲイト宣言が必要です。
        /// </summary>
        public delegate bool MyDelegate(
            int param1, 
            int param2,
            ref int param3);

            MyDelegate asyncCall;

        /// <summary>
        /// AsyncronousCall は、非同期メソッドです。
        /// このメソッドが BeginInvoke により非同期呼び出しされます。
        /// BeginInvoke でこのメソッドを渡す必要があるので、デリゲイト宣言が必要です。
        /// パラメータは任意です。
        /// </summary>
        /// <param name="param1"></param>
        /// <param name="param2"></param>
        /// <param name="param3"></param>
        /// <returns></returns>
        public bool AsyncronousCall(int param1, int param2, ref int param3)
        {
            Console.WriteLine("AsyncronousCall");

            // この非同期呼び出しは、スレッドプールに作られる。
            // そこで、GetAvailableThreadsメソッドによりスレッドプールを調べる。
            // workerThreads: スレッドプールに追加可能なワーカー スレッドの数
            // competionPortThreads: 非同期 I/O スレッドの数
            int workerThreads, competionPortThreads;
            System.Threading.ThreadPool.GetAvailableThreads
                (out workerThreads, out competionPortThreads);
            Console.WriteLine("AsyncronousCall: workerThreads:{0}, 
                competionPortThreads:{1}", workerThreads, competionPortThreads);

            // 重い処理
            for(int i = 0; i < 5; i++)
            {
                Console.WriteLine(i);
            System.Threading.Thread.Sleep(1000);
            }
            param3 = param1 * param2;
            return true;
        }
    }
}