読者です 読者をやめる 読者になる 読者になる

Control.BeginInvoke

ButtonやTextBoxなどの基底クラスとなるControlクラスにも、BeginInvokeはある・・・。

Control.BeginInvoke メソッド (Delegate) (System.Windows.Forms)

これも非同期処理に関するものだが、C#コンパイラの用意するBeginInvokeとはちょっと違い、非同期処理を実行するワーカースレッドからコントロールの操作を行う時に使う・・・。

f:id:BG1:20170328073727p:plain

今回はWindowsアプリケーション・・・。

f:id:BG1:20170328073824p:plain

Buttonを、

f:id:BG1:20170328073917p:plain

大きく配置・・・。

f:id:BG1:20170328073942p:plain

フォントも24と大きく・・・。

f:id:BG1:20170328074050p:plain

テキストも、

f:id:BG1:20170328074234p:plain

"開始"に変更・・・。

button1のClickに対するハンドラは、

        // button1がクリックされた時.
        private void button1_Click(object sender, EventArgs e)
        {

            // "開始"から"実行中..."へ.
            button1.Text = "実行中...";    // button1.Textに"実行中..."をセット.

            // 10秒待つ.(擬似的な重たい処理.)
            Thread.Sleep(10000);    // Thread.Sleepで10秒休止.

            // "実行中..."から"開始"に.
            button1.Text = "開始";  // button1.Textに"開始"をセット.

        }

最初、こうしてみる・・・。
Thread.Sleepで10秒かかる疑似的に重たい処理、前後でテキストを変化させる・・・。

f:id:BG1:20170328110131p:plain

このように、重たい処理をUIスレッド上で行うと、テキストは変化しないし、固まってしまう・・・。

前回までにやったBeginInvokeやAsyncCallbackを使って、重たい処理を非同期化する・・・。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Runtime.Remoting.Messaging;    // AsyncResultクラス(System.Runtime.Remoting.Messaging名前空間)
using System.Text;
using System.Threading; // マルチスレッド(System.Threading名前空間)
using System.Windows.Forms;

// デリゲートの定義
delegate void AsyncButtonDelegate(); // 引数も戻り地も無い関数を持つデリゲートAsyncButtonDelegate.

namespace Control_
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // button1がクリックされた時.
        private void button1_Click(object sender, EventArgs e)
        {

            // 非同期処理で実行するデリゲートasyncButtonの生成.
            AsyncButtonDelegate asyncButton = new AsyncButtonDelegate(AsyncButton); // AsyncButtonを呼ぶAsyncButtonDelegateオブジェクトasyncButtonの生成.

            // 非同期処理の完了後に実行するAsyncCallbackデリゲートcallbackの生成.
            AsyncCallback callback = new AsyncCallback(AsyncCallbackFunc);  // 非同期処理完了後にAsyncCallbackFuncを実行するAsyncCallbackオブジェクトcallbackを生成.

            // 非同期処理の開始.
            asyncButton.BeginInvoke(callback, null);    // asyncButton.BeginInvokeで非同期処理開始(非同期処理完了後に実行するcallbackを渡している.)

        }

        // 非同期処理ボタンメソッドAsyncButton.
        public void AsyncButton()
        {

            // "開始"から"実行中..."へ.
            button1.Text = "実行中...";    // button1.Textに"実行中..."をセット.

            // 10秒待つ.(擬似的な重たい処理.)
            Thread.Sleep(10000);    // Thread.Sleepで10秒休止.

            // "実行中..."から"終了"に.
            button1.Text = "終了";  // button1.Textに"終了"をセット.

        }

        // 非同期処理完了後のコールバックメソッドAsyncCallbackFunc.
        public void AsyncCallbackFunc(IAsyncResult ar)
        {

            // IAsyncResult型arをAsyncResultにキャスト.
            AsyncResult asyncRes = (AsyncResult)ar; // arをAsyncResult型asyncResにキャスト.

            // 非同期処理で使ったデリゲートを取得.
            AsyncButtonDelegate asyncButton = (AsyncButtonDelegate)asyncRes.AsyncDelegate;   // asyncRes.AsyncDelegateをキャストしてasyncButtonを取得.

            // 非同期処理の完了.
            asyncButton.EndInvoke(ar); // asyncButton.EndInvokeで完了.

            // 3秒待つ.(元に戻す処理.)
            Thread.Sleep(3000);    // Thread.Sleepで3秒休止.

            // "終了"から"開始"に.
            button1.Text = "開始";  // button1.Textに"開始"をセット.

        }
    }
}

button1がクリックされたら、asyncButton.BeginInvokeでAsyncButtonが実行され、それが終ったら渡したcallbackが持つAsyncCallbackFuncが呼ばれる・・・。

しかし、これを実行すると、

f:id:BG1:20170328111356p:plain

このような例外が出てしまう・・・。
テキストを変更するなどのコントロールを操作する処理はUIスレッドで行わないといけないからである・・・。
(AsyncCallbackFuncはUIスレッドだと思っていたが、どうもこれも例外が出たのでワーカースレッドらしい・・・。)

そこでControl.BeginInvokeである・・・。
引数に渡したデリゲートが持つメソッドがそのControlのスレッド(UIスレッド)で実行されるのである・・・。

AsyncButtonの中で、button1.BeginInvokeにrunningを渡すと、Runningが実行されて"実行中..."になり、finishを渡すと、Finishが実行されて"終了"になり、AsyncCallbackFuncの中ではあるが、startを渡すとStartが実行されて"開始"になる・・・。
(今回は引数なしバージョンを使ったので、いっぱいデリゲート作ってしまったが、引数ありバージョンなら表示するテキストを渡すだけの1つのデリゲートで済む・・・。)

f:id:BG1:20170328122438p:plain

押すと、

f:id:BG1:20170328122529p:plain

"実行中..."になり、しばらく待つと、

f:id:BG1:20170328122608p:plain

"終了"となり、ちょっと待つと、

f:id:BG1:20170328122651p:plain

"開始"に戻る・・・。

Sample/dotnet/Control/BeginInvoke/src/Control_ at master · bg1bgst333/Sample · GitHub