UIのスレッド外からの操作

Last update:

UIを生成したスレッドとは異なるスレッドから、UIを操作しようとすると、例外がおきます。
操作をUIスレッドから行うために、delegateを定義して自分自身をInvokeし直す必要があります。
例えば、以下のような感じです。

delegate void setTextDelegate(string str);
private void setText(string str)
{
    if (this.InvokeRequired)//別スレッドから呼び出されたとき Invokeして呼びなおす
    {
        setTextDelegate d = new setTextDelegate(setText);
        this.Invoke(d, str);
        return;
    }
    textBox1.Text = str;
}

Action(やFunc)を利用すると、少し書く量が減ります。

private void setText(string str)
{
    if (this.InvokeRequired)//別スレッドから呼び出されたとき Invokeして呼びなおす
    {
        this.Invoke(new Action(() => setText(str)), null);
        return;
    }
    textBox1.Text = str;
}

ところで別スレッドからのアクセスが起きる可能性のある関数が大量にある場合、上記のような変更を加えるのは、なかなか面倒です。なるべくなら、元の関数にはなるべく手を加えず、余計な条件分岐を減らしたいものです。
そんな時、ラムダ式をExpressionで受け取る以下のような関数executeを同一クラス内に定義しておけば、コードがシンプルになります。

private void execute(Expression<Action> expression)
{
    if (this.InvokeRequired)
        this.Invoke(expression.Compile(), null);
    else
        expression.Compile().DynamicInvoke(null);
}
private void setText(string str) //新しい関数。中身はExecuteを呼び出すだけ。
{
    execute(() => _setText(str));
}
private void _setText(string str) //元の関数には _ (アンダーバー)を付けておく
{
    textBox1.Text = str;
}

元の関数の名前を変更する必要はありますが、中身の変更は一切なしです。
戻り値が必要な場合は、以下のような感じにすればいいでしょう。

private Type execute<Type>(Expression<Func<Type>> expression)
{
    if (this.InvokeRequired)
        return this.Invoke(expression.Compile(), null);
    else
        return expression.Compile().DynamicInvoke(null);
}
private string getText() //新しい関数。中身はExecuteを呼び出すだけ。
{
    return execute<string>(() => _getText());
}
private string _getText() //元の関数には _ (アンダーバー)を付けておく
{
    return textBox1.Text;
}

Expressionをコンパイルする手間がかかりますので、パフォーマンスは少し落ちますが、コーディング量が減るのがメリットです。

Seto's Page

Stupidity has a certain charm; ignorance does not. Frank Zappa