« [C# Advent Calendar 2011] Win8 に備えて async / await を勉強してみよう | トップページ | [メモ] Windows 8 と Visual Studio 11 Express の Developer Preview (CTP) を入れてみた。 »

2011年12月 9日 (金)

[.NET Framework 4/4.5][C# 5] 非同期で動くビジネスロジックを作る

C# Advent Calendar 2011 参加記事 「Win8 に備えて async / await を勉強してみよう」 で書いたように、 async / await が使えれば非同期処理のコーディングがあっさり出来てしまいます。 たとえば今まで 3秒も掛かっていた UI のイベントハンドラーに async / await を付けるだけで、 ほぼ 0秒で応答が返ってくるようになります (画面が書き換わるのは、 やっぱり 3秒後ですけど)。

20111209_asyncmethod01a
※ ユニットテストのコードでイベントハンドラーの雰囲気を出してみた。
※ 「LongTimeMethodTest_既存のUIのイベントハンドラーの例だと思ってほしい」は、実行に 3秒。
※ 「LongTimeMethodAsyncTest_UIのイベントハンドラーはこんな感じになる」の方は、0mS (1ミリ秒掛かっていない)。

こういう嬉しいことが出来るのは、 非同期実行してくれる 「~Async()」 という名前のメソッドが用意されてるから、 ですね。 (以降、 めんどくさいので 「Async メソッド」 と呼ぶことにします。)

WinRT では、 インターネットやファイルアクセスなどに、 多くの Async メソッドが提供されます。 ですが、 そういった個別の I/O を非同期にしたいという場面は、 じつはそれほど多くありません ( 前記事で出した、 並行してダウンロードしたいときとか )。
本当に欲しいのは、 UI から呼び出すビジネスロジック全体が Async メソッドになっていること、 です。

既存の、 非同期実行のことなど考えていないビジネスロジックを、 Async メソッドに仕立て直すことは可能でしょうか?

次のようなビジネスロジックのメソッドがあったとします。 同期で動きますので、 そのままUIのイベントハンドラーから呼び出すと、 この LongTimeMethod() が処理を終えるまで画面はフリーズしてしまいます。

【LongTimeMethod()】

これをラップした Async メソッドを、 簡単に作ることができます。 これは、 .NET Framework 4.0 (Visual Studio 2010) から使えるようになった、 Task Parallel Library (TPL) の機能です。

【LongTimeMethodAsync()】

たった 1行です

これだけで Async メソッドが出来てしまうのです。 もっとも、 Task.Run() は .NET Framework 4.5 で追加されるメソッドなので、 .NET Framework 4.0 では Task.Factory.StartNew() と書かねばなりませんが、 どちらにせよメソッドの中身は 1行で済んでしまいます。
※ 参考: MSDN Blogs - Parallel Programming with .NET "Task.Run vs Task.Factory.StartNew" (2011/10/25)

そして、 これを呼び出すイベントハンドラーは、 C# 5 では async / await を使って、 こんなふうに書けるわけです。

【Button1_Click()】

これで、 ボタンをクリックすると処理に数秒掛かる LongTimeMethod() が呼び出されますが、 画面はフリーズすることなく、 数秒経ったところで OutputValue にバインドされているコントロールの値が変わるというわけです。

一般的なアプリでは、 これで十分なことが多いでしょう。 ただし、 ビジネスロジックが処理中に画面を操作されて、 また同じロジックや別のロジックが要求されたらどうなるか? そこはしっかり考えないといけません。


[まとめ]

  • Async メソッドは、 Visual Studio 2010 で簡単に作れる。
  • Async メソッドの呼び出しは、 C# 5 (Visual Studio 11) の async / await で簡単に書けるようになる。
  • 非同期処理が簡単に書けるといっても、 非同期処理を使う画面の設計が簡単になったわけではない。

 


ところで、  Task Parallel Library (TPL) には非同期実行中に処理をキャンセルするための仕掛けもあります。 これは、 既存のロジックそのまま、 とはいきません。 長い処理中に、 キャンセルがリクエストされているかどうかチェックするコードを、 適宜追加する必要があります。

まず、 ラッパーの Async メソッドはこうなります。

【LongTimeMethodAsync() キャンセル可能バージョン】

引数として CancellationToken も受け取るようにし、 それをロジックのメソッドと Task.Factory 自体にも渡してやります。
ビジネスロジックは、 次のようにして、 要所要所でキャンセルのリクエストに応えられるように改修します。

【LongTimeMethod() キャンセル可能バージョン】
※ (2011/1218 追記) キャンセル時に、 何か後始末が必要なことを考えて if (token.IsCancellationRequested){ token.ThrowIfCancellationRequested(); } としましたが、 ぐぐってみるとtoken.ThrowIfCancellationRequested(); の 1行だけで済ませてるコードばっかり。 入口のメソッドで catch して後始末すればいいってことかな。

これを呼び出す側は、 ちょっと面倒になります。 CancellationTokenSource に CancellationToken を作ってもらい、 Async メソッドの呼び出し時にそれを渡します。 キャンセルのリクエストは CancellationTokenSource にお願いします。 Wait() するときも、 CancellationToken を渡します。

実際のコード例を、 次に示します。

【キャンセル可能な Async メソッドの使い方】

なお、 上記のような改修も面倒ですが、 印刷処理やデータ更新処理などのキャンセルは、 ビジネスロジック自体を考える方がもっと複雑です。

|

« [C# Advent Calendar 2011] Win8 に備えて async / await を勉強してみよう | トップページ | [メモ] Windows 8 と Visual Studio 11 Express の Developer Preview (CTP) を入れてみた。 »

プログラミング」カテゴリの記事

* プログラミング ( Metro スタイル )」カテゴリの記事

-プログラミング ( VS2012 )」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)


コメントは記事投稿者が公開するまで表示されません。



トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/209349/53444630

この記事へのトラックバック一覧です: [.NET Framework 4/4.5][C# 5] 非同期で動くビジネスロジックを作る:

« [C# Advent Calendar 2011] Win8 に備えて async / await を勉強してみよう | トップページ | [メモ] Windows 8 と Visual Studio 11 Express の Developer Preview (CTP) を入れてみた。 »