【C#】Delegate / Event ~ 非同期デリゲート・コールバック関数を実装する ~

■ はじめに

https://dk521123.hatenablog.com/entry/2010/12/12/164101
https://dk521123.hatenablog.com/entry/2010/12/25/221009
https://dk521123.hatenablog.com/entry/2010/10/22/101350

の続き。

今回は、処理が終わったら、
コールバック関数を呼び出して実行結果を得る

 ■ 使用上の注意

 * メインスレッドではなく、コントロールを作成していない別スレッドから
   コントロールに直接呼び出しはできない
 => 以下のような「例外内容」が発生する

 例外内容

System.InvalidOperationException: '有効ではないスレッド間の操作:
 コントロールが作成されたスレッド以外のスレッドからコントロール 'label3' がアクセスされました。'

 参考文献
http://cammy.co.jp/technical/2017/04/25/c-%E3%82%B5%E3%83%96%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E3%81%8B%E3%82%89%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%81%AE%E8%A1%A8%E7%A4%BA%E3%82%92%E8%A1%8C%E3%81%86invoke/
公式サイト
https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls

 ■ サンプル

 例1

呼び出し側

using System;
using System.Runtime.Remoting.Messaging;
using System.Windows.Forms;

namespace SampleForm
{
  // 呼び出し側
  public partial class Form1 : Form
  {
    // 非同期実行するためのデリゲート
    private delegate string SampleDelegate(int sleep, string format);

    public Form1()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      HeavyTask heavyTask = new HeavyTask();

      // 実行するデリゲートを作成
      SampleDelegate sampleDelegate =
          new SampleDelegate(heavyTask.DelegatingMethod);

      // コールバック関数
      AsyncCallback callback = new AsyncCallback(this.CallbackMethod);

      this.label1.Text = "Before calling BeginInvoke()";

      // 非同期実行の呼び出し
      IAsyncResult async =
          sampleDelegate.BeginInvoke(5000, "yyyy/MM/dd", callback, null);

      this.label2.Text = "Called BeginInvoke()";
    }

    // コールバック関数:スレッド終了後の処理を記述
    private void CallbackMethod(IAsyncResult async)
    {
      // AsyncResultに変換
      AsyncResult asyncResult = async as AsyncResult;

      // 非同期の呼び出しが行われたデリゲート オブジェクトを取得
      SampleDelegate sampleDelegate =
          asyncResult.AsyncDelegate as SampleDelegate;

      // 処理結果取得
      this.Invoke(new MethodInvoker(
          delegate
          {
            string result = sampleDelegate.EndInvoke(async);
            this.label3.Text = "Today is " + result;
          }));
      // 以下の処理だと「this.label3.Text = "Today is " + result;」で例外発生
      // string result = sampleDelegate.EndInvoke(async);
      // this.label3.Text = "Today is " + result;
    }
  }
}

呼び出され側

using System;

namespace SampleForm
{
  public class HeavyTask
  {
    // 非同期させたい(重たい)処理
    public string DelegatingMethod(int sleep, string format)
    {
      System.Threading.Thread.Sleep(sleep);
      return DateTime.Now.ToString(format);
    }
  }
}

出力結果

【ボタン押下直後】
Before Calling BeginInvoke()
label2
label3

【ボタン押下5秒後】
Before Calling BeginInvoke()
Called BeginInvoke()
Today is 2019/03/09

 例1の別解

using System;
using System.Runtime.Remoting.Messaging;
using System.Windows.Forms;

namespace SampleForm
{
  // 呼び出し側
  public partial class Form1 : Form
  {
    // 非同期実行するためのデリゲート
    private delegate string SampleDelegate(int sleep, string format);

    private delegate void SetLabelTextForResultDelegate(string result);
    private SetLabelTextForResultDelegate delegateForThreadSafe;

    public Form1()
    {
      InitializeComponent();

      this.delegateForThreadSafe = new SetLabelTextForResultDelegate(this.SetLabelText);
    }

    private void button1_Click(object sender, EventArgs e)
    {
      HeavyTask heavyTask = new HeavyTask();

      // 実行するデリゲートを作成
      SampleDelegate sampleDelegate =
          new SampleDelegate(heavyTask.DelegatingMethod);

      // コールバック関数
      AsyncCallback callback = new AsyncCallback(this.CallbackMethod);

      this.label1.Text = "Before calling BeginInvoke()";

      // 非同期実行の呼び出し
      IAsyncResult async =
          sampleDelegate.BeginInvoke(5000, "yyyy/MM/dd", callback, null);

      this.label2.Text = "Called BeginInvoke()";
    }

    // コールバック関数:スレッド終了後の処理を記述
    private void CallbackMethod(IAsyncResult async)
    {
      // AsyncResultに変換
      AsyncResult asyncResult = async as AsyncResult;

      // 非同期の呼び出しが行われたデリゲート オブジェクトを取得
      SampleDelegate sampleDelegate = asyncResult.AsyncDelegate as SampleDelegate;
      string result = sampleDelegate.EndInvoke(async);
      this.Invoke(this.delegateForThreadSafe, new object[] { result });
    }

    private void SetLabelText(string result)
    {
      this.label3.Text = "Today is " + result;
    }
  }
}

 関連記事

Delegate / Event ~ 入門編 / Delegate
https://dk521123.hatenablog.com/entry/2010/12/12/164101
Delegate / Event ~ 入門編 / Event ~
https://dk521123.hatenablog.com/entry/2010/12/25/221009