C#, UIコントロールをUIスレッド以外からアクセスする方法
Windowsフォームアプリでよく発生する例外の1つが、UIスレッド以外からUIコントロールにアクセスしようとして発生するInvalidOperationException例外。ここではその対処法について書く。
System.InvalidOperationException: ‘有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール ‘Form1’ がアクセスされました。
例えば、下記のコードを実行すると、例外が発生する。
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Task.Run(() =>
{
label1.Text = "Hello from another thread!";
});
}
}
}

“System.InvalidOperationException: ‘有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール ‘Form1’ がアクセスされました。'”
メインスレッドとワーカースレッド
例外を発生させないためには、UIスレッド(メインスレッド)以外のスレッド(ワーカースレッド)からUIコントロールにアクセスさせない。今、どのスレッドが実行されているのかは、スレッドウィンドウに表示される。

UIスレッド以外で実行されている場合、Invokeを使ってUIスレッドに切り替えて実行させるようにする。ここでは、具体的な実装を書いてみる。
戻り値なし: Invoke(new Action<引数の型>(メソッド名), 引数名)
戻り値なしの場合はActionを使う。
例えば、上記の例であれば下記のように書き換えれば良い。
UIコントロールへのアクセス部分をメソッド化し、InvokeRequiredがtrueの場合(つまりUIスレッド以外で実行している場合)、Invokeを使った再帰呼び出しの結果、UIスレッドで実行させることが出来る。
return; を書き忘れると例外が発生するので注意。
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Task.Run(() =>
{
UpdateTextBox("Hello from another thread!");
});
}
private void UpdateTextBox(string message)
{
if (label1.InvokeRequired)
{
label1.Invoke(new Action<string>( UpdateTextBox), message );
return;
}
label1.Text = message;
}
}
}
戻り値あり: Invoke(new Func<引数の型,戻り値の型>(メソッド名)[, 引数名])
戻り値ありの場合はFuncを使う。Invokeの戻り値はobject型なので、returnする前に型を返したい型にキャストしてあげる必要がある。
例えば、下記のように書く。
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
string message = "";
Task.Run(() =>
{
message = GetLabelText();
});
}
private string GetLabelText()
{
if (label1.InvokeRequired)
{
object text = label1.Invoke(new Func<string>(GetLabelText));
return (string)text;
}
return label1.Text;
}
}
}