【C#】UIスレッド以外からUIのコントロールを操作する

  • 2020.11.11
  • C#
【C#】UIスレッド以外からUIのコントロールを操作する

マルチスレッドで動くGUIアプリのプログラムを書いていると、以下のようなエラーに出くわすことがある人が多いのではないでしょうか。

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

処理が重い!並列処理にしよう!⇒なんかエラーでた!⇒ググる⇒invoke使えばいいんだ!
過去の自分を含め、多分みんなこの流れで解決まではたどり着く。でも、何がだめだったのか、どうして改善できるのか、invoke, delegateとは一体何なのかという事までちゃんと理解する人はあまりいないと思う。

本記事では、まずは方法を示して、スレッド間の処理の移譲の仕組みを深堀してみる。

エラーが出る例

テキストボックスとボタンだけが配置されたUIを作り、ボタンを押したらテキストボックスに”test”と表示される処理を実装した。

テキストボックスの文字を変更する関数SubThreadProcess()Task.Run()によって別スレッド(ワーカースレッド)で起動される。

このプログラムを起動し、ボタンを押すと下記の位置で例外が発生する。

解決方法

はい。invokedelegateを使ってください(笑)

以下のように修正する。

なぜエラーになるのか

最初に示したエラーになるコードの内容を整理してみる。


ユーザーがボタンをクリックするとイベントハンドラbutton1_Click()が実行される。
次に、Task.Run()によってワーカースレッドが立ち上がり、SubThreadProcess()が実行される。
この関数の中でテキストボックスの内容を変更しようとするところでエラーが発生する。

まず、C#のGUIアプリケーションプログラミングの大原則は

UIコントロールの変更はUIスレッドからのみ行える

ということ。これはこの言語、フレームワークの仕様である。

今回はテキストボックスの内容を”ワーカースレッドから”変更しようとしたことでエラーになってしまっている。

invokeでなぜ解決するのか

エラーが出る原因はただ一点、UIスレッド以外でUIの変更を行うからであった。であれば、UIスレッドで処理を行うようにワーカースレッドからUIスレッドへ処理を依頼すればよい。

UIスレッドへ処理を依頼するときに使うのがControl.Invokeメソッド。

https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.control.invoke?view=netcore-3.1

本プログラムではthis.Invoke(process);という形で利用している。
thisはオブジェクトForm1を示す。Form1はテキストボックスやボタンなどUIコントロールを持つフォームのクラスである。そのクラスのメソッドの一つとしてinvoke()が存在している。

invokeの引数にはUIスレッドに依頼したい処理を渡す。

しかし、this.Invoke(ChangeTextBox);のように、関数をそのまま引数として渡すことはできない。
上記URLの公式ドキュメントを見ると、引数として渡すデータ型はdelegateということになっている。

じゃあ、そのdelegateって何なのか。

delegateとは

delegateは「関数を格納する箱」のようなデータ型と考えればいい。
正確には「関数の呼び出し先を格納する箱」。

関数をこの「箱」に入れて、箱をinvokeに引数として渡すことでUIスレッドで処理を実行できる。

delegateはまず宣言が必要。関数を格納する箱を定義する。

ここまでだとデータ型を定義しただけの状態、つまり箱の形を定義しただけの状態である。

次に、定義した箱の実体(process)を宣言して、関数を格納(登録)する。

たったこれだけ。あとは関数が登録された状態の箱processInvoke()に渡してやればいい。

delegateについては別記事で詳しく解説してありますので、併せて参照して頂ければと思います↓

C#カテゴリの最新記事