データ バインディングと非同期処理 (その 2)

前回のデータ バインディングと非同期処理 (その 1) からの続きです。

前回は、UI スレッドで重い処理を実行してフリーズしてしまうアプリを作成しました。
今回は、非同期処理を使ってフリーズしないアプリに変更する方法について記述します。

 

■ TAP メソッドが提供されている場合

まず、ライブラリなどでタスクベースの非同期パターン (TAP) のメソッドが提供されている場合を考えます。
TAP メソッドが実装されているクラスの例としては、HttpClient クラスなどが挙げられます。
今回の例では、ConvertUtility クラスに ToUpperAsync メソッドが追加されたとします。

ConvertUtility.cs


public static class ConvertUtility

    // 同期メソッド。
    public static string ToUpper(string text)
    {
        Thread.Sleep(3000);
        return text.ToUpper();
    } 

    // TAP メソッド。
    public static Task<string> ToUpperAsync(string text)
    {
        return Task.Run(() => ToUpper(text));
    }
}


 

この場合は対応が非常に簡単です。
TextModel クラスのコンストラクターの中で、以前は

AddPropertyChangedHandler("Input", () => Output = ConvertUtility.ToUpper(Input));

としていたところを、次のように変更します。

AddPropertyChangedHandler("Input", async () => Output = await ConvertUtility.ToUpperAsync(Input));

async/await キーワードと、メソッド名の Async が追加されただけです。
このようにすると、時間のかかる処理はバックグラウンド スレッドで実行され、完了すると UI スレッドに戻ってきます。

実行してみると、アプリはすぐに起動し、下側の TextBlock には非同期的に値が反映されるようになります。

実行結果

 

■ 非同期メソッドが提供されていない場合

上の例の ToUpperAsync のようなメソッドが提供されておらず、ToUpper メソッドを使わざるを得ないような場合でも、
Task.Run メソッドを使えば対応できます。

TextModel クラスのコンストラクターの処理を次のように変更します。

AddPropertyChangedHandler("Input",
    async () => Output = await Task.Run(() => ConvertUtility.ToUpper(Input)));

Task.Run メソッドに渡した処理が完了すると UI スレッドに戻ってきます。

 

⋄ async/await を使わない方法

次のようなコードに変更しても、アプリはフリーズせずに動作します。

AddPropertyChangedHandler("Input",
    () => Task.Run(() => Output = ConvertUtility.ToUpper(Input)));

これだと Output プロパティにはバックグラウンド スレッドで値が設定されるため、
普通に考えると、UI 要素にアクセスできずに例外が発生してしまうように見えます。

しかし実は、Binding オブジェクトはデータソースの値を UI スレッドで取得します。
つまり Output プロパティの値を取得するときに UI スレッドに移るため、問題なく動作します。

 

■ EAP メソッドが提供されている場合

さらに、イベントベースの非同期パターン (EAP) の場合も考えてみましょう。
おそらく、現実にはまだ EAP にしか対応していないライブラリも少なくないと思います。

ConvertUtility クラスに対する EAP の実装として、次のクラスを作成します。

ConvertClient.cs


public class ConvertClient
{
    public event EventHandler<string> ToUpperCompleted = (o, e) => { };

    // EAP メソッド。
    public void ToUpperAsync(string text)
    {
        Task.Run(() => ConvertUtility.ToUpper(text))
            .ContinueWith(t => ToUpperCompleted(this, t.Result));
    }
}


 

TextModel クラスのコンストラクターの処理を次のように変更します。

var client = new ConvertClient();
client.ToUpperCompleted += (o, e) => Output = e;
AddPropertyChangedHandler("Input", () => client.ToUpperAsync(Input));

ToUpperCompleted イベントのイベント ハンドラーはバックグラウンド スレッドで実行されますが、
上述の通り、UI 側は Output プロパティの値を UI スレッドで取得します。

今回のイベント ハンドラーはバックグラウンド スレッドで実行されていますが、
既存のライブラリの中には、イベント ハンドラーが UI スレッドで実行されるものもあります。
いずれの場合でも、上記のコードで対応できます。

 

■ 注意点

  • 非同期処理は同時に複数実行されることもあるため、場合により同時実行制御 (lock など) が必要となります。
  • ここでは説明しませんでしたが、非同期プログラミング モデル (APM) の場合も同様に対応できると思います。

 

バージョン情報
C# 5.0
.NET Framework 4.5

参照
非同期プログラミングのパターン
Async/Await – 非同期プログラミングのベスト プラクティス (MSDN マガジン)
.NETで非同期ライブラリを正しく実装する
async/awaitと同時実行制御

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。