かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WPF4.5入門 その41 「DispatcherObject」

コントロールの使い方ばかりを書くのも飽きてきたので趣向を変えていきます。

DispatcherObject

WPFでは、他のUIフレームワークと同様にUIを操作するには専用のスレッドから操作をする必要があります。WPFでは、この操作を簡単にするためにDispatcherという仕組みを提供しています。WPFの継承階層を上へ上へたどっていくとDispatcherObjectというクラスに必ず当たります。このクラスにはDispatcherというプロパティが定義されていて、このオブジェクトが生成されたスレッドから操作されているかをチェックする仕組みや、オブジェクトが生成されたスレッドで処理のキューイングを行う仕組みを提供しています。

DispatcherObjectで提供される主な機能を以下に示します。

プロパティ名 説明
public Dispatcher Dispatcher { get; } DispatcherObjectに紐づけられたスレッドに対応するDispatcherオブジェクトを取得します。
メソッド名 説明
public void VerifyAccess() 現在のスレッドがDispatcherObjectに紐づけられたスレッドかどうかチェックします。チェックの結果異なるスレッドの場合InvalidOperationExceptionの例外をスローします。
public bool CheckAccess() 現在のスレッドがDispatcherObjectに紐づけられたスレッドかどうかチェックします。チェックの結果異なるスレッドの場合falseを返します。

これらの機能の使い方についてのサンプルプログラムを以下に示します。 DispatcherObjectは抽象クラスなので、継承してメソッドを1つもつクラスを作成しました。メソッド内では、VerifyAccessメソッドを使って有効なスレッドからのアクセスかどうかを確認して、デバッグウィンドウへメッセージを出力しています。

public class DrivedObject : DispatcherObject
{
    public void DoSomething()
    {
        // UIスレッドからのアクセスかチェックする
        this.VerifyAccess();
        Debug.WriteLine("DoSomething");
    }
}

Windowに3つのボタンを置いて、UIスレッドからの直接呼出し、UIスレッド以外からの呼び出し、UIスレッド以外からDispatcher経由での呼び出しの3パターンの呼び出しを確認します。

<Window x:Class="DispatcherObjectSample01.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <Button Content="UIスレッドからなのでOK" Click="OKButton_Click" />
        <Button Content="UIスレッド以外から呼ぶのでNG" Click="NGButton_Click" />
        <Button Content="UIスレッド以外からDispatcher経由で呼ぶのでOK" 
Click="DispatcherButton_Click" />
    </StackPanel>
</Window>
private void OKButton_Click(object sender, RoutedEventArgs e)
{
    // UIスレッドからの普通の呼び出しなのでOK
    var d = new DrivedObject();
    d.DoSomething();
}

private async void NGButton_Click(object sender, RoutedEventArgs e)
{
    // UIスレッド以外からの呼び出しなので例外が出る
    var d = new DrivedObject();
    try
    {
        await Task.Run(() => d.DoSomething());
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);
    }
}

private async void DispatcherButton_Click(object sender, RoutedEventArgs e)
{
    // UIスレッド以外だがDispatcher経由での呼び出しなのでOK
    var d = new DrivedObject();
    await Task.Run(async () =>
    {
        if (!d.CheckAccess())
        {
            await d.Dispatcher.InvokeAsync(() => d.DoSomething()); // OK
        }
    });
}

実行して上から順番にボタンを押した結果のデバッグウィンドウを以下に示します。

DoSomething
...省略...
System.InvalidOperationException: このオブジェクトは別のスレッドに所有されているため、呼び出しスレッドはこのオブジェクトにアクセスできません。
...省略...
DoSomething

最初のボタンでは、UIスレッドからの呼び出しなので、エラーもなくデバッグウィンドウに結果が出ています。二番目のボタンでは、UIスレッド以外からの呼び出しでVerifyAccessの箇所でInvalidOperationExceptionが発生しています。三番目のボタンでは、UIスレッド以外からDispatcher経由で、呼び出しているためデバッグウィンドウに結果が出ていることが確認できます。

通常のWPFを使った開発でも、UIスレッド以外からUIを直接または間接的に操作することがあります。そんなときは、このDispatcherを使い今回のサンプルプログラムのように呼び出す必要があります。

過去記事