5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Windows Formsで UIスレッドかどうか判定する。Control.Invokeは何をし、InvokeRequiredは何をどう調べているのか?

Last updated at Posted at 2019-03-08

はじめに

Windows Forms アプリケーションなどでマルチスレッドアプリケーションを作成するときの注意として、フォームやコントロールへは、それらを作成したスレッドからアクセスする必要があります。

お題のアプリケーション

こんな感じのフォームで、1秒ごとに現在時刻を更新表示するアプリケーションを作成してみます。

image.png

フォームを作成したスレッドからタスクを生成(=別のスレッド)して更新表示をさせるしくみの、こんな感じのコードを実行させると、タスク内でラベルコントロール (label1) のテキストを更新しようとしたとき「InvalidOperationException : 有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール 'label1' がアクセスされました。」例外が発生してしまいます。

image.png

Form1.cs
using System;
using System.Threading.Tasks;
using System.Windows.Forms;

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

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            Task.Run(() => 
            {
                for(; ; )
                {
                    label1.Text = DateTime.Now.ToString("HH:mm:ss"); // <- 例外発生
                    System.Threading.Thread.Sleep(1000);
                }
            });
        }
    }
}

専ら作業用に作成したスレッド(ワーカースレッド)から、フォームやコントロールなどが属するスレッド(UIスレッド)に処理をさせるには、Control.Invoke メソッドを使用する必要があり、ラベルコントロールのTextプロパティへ書き込む処理のコードを次のように変更するとうまく動きます。

            Task.Run(() =>
            {
                for (; ; )
                {
                    Invoke((MethodInvoker)delegate
                    {
                        label1.Text = DateTime.Now.ToString("HH:mm:ss");
                    });
                    System.Threading.Thread.Sleep(1000);
                }
            });

果たして、この Invoke メソッドは、どんなことをしているのでしょうか?

Invokeメソッドの処理

.NET Framework (4.7.2) のソースコードを読んでみました。かなりおおまかに書くと次のことをしています。

  1. Win32API の GetWindowsThreadProcessId 関数を使用して、アクセスしようとしているスレッドと、対象のコントロール(ハンドルが作成されていなければその親コントロール)の属するスレッドが一致しているか調べる。
  2. 処理するメソッド情報を溜めるキューにメソッド情報(メソッド、引数、実行コンテキスト、戻り値や発生した例外の受け取り場所、System.IAsyncResult をインプリメント)を追加し、1.でスレッドが異なればウインドウメッセージ(スレッドコールバック用:4.7.2では"WindowsForms12_ThreadCallbackMessage")を POST する。1.で同じスレッドであれば、自分自身でキューの中身を取り出してメソッドを呼び出す(キューが空になるまで繰り返す)。
  3. たったいまキューに追加した対象のメソッドが終了するまで待機(IAsyncResult.AsyncWaitHandle)し、その戻り値を返す。

POSTされた先のUIスレッドのメッセージポンプ(WndProc)では、スレッドコールバック用のウインドウメッセージを受け取ったら、キューを取り出してメソッドを(キューが空になるまで繰り返し)呼び出します。

ちなみに Control.InvokeRequired プロパティは、上記のうち 1. の処理を行い、同じスレッドであれば false、異なれば true を返します。

おわりに

ちょっと興味がありましたので、調べてみました。.NET Framework のソースコードを簡単に調べられる、Visual Studio の拡張機能「Ref12」にはいつもお世話になっています。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?