Visual Studio 2015 Community で WCF のサービス参照を追加して生成されるサービスプロキシには非同期版のメソッドも含まれ、それを利用すれば async / await キーワードを付与することで簡単に非同期呼び出しができるという話を書きます。
何を今さらと言われるかもしれませんが、自分としては新発見で、これは備忘録として残しておかねばと思いましたので。(笑)
上の画像は Microsoft のチュートリアル「10 行でズバリ!! [C#] WCF サービスの作成と利用」をベースに作った WPF クライアントアプリです。string 型のデータを応答として返す WCF サービスを呼び出して、その応答を表示しています。
上のチュートリアルにある通り、クライアントアプリから WCF サービスを呼び出すのに利用するサービスプロキシを生成するため、サービス参照の追加を行います。
その際、「サービス参照の追加」ダイアログで[詳細設定(V)...]をクリックすると表示される「サービス参照設定」ダイアログを見ると、Visual Studio 2015 Community ではデフォルトで以下の画像の設定になっています。
この設定画面で[非同期操作の生成を許可する(P)]にチェックが入っていて、[タスクベースの操作を生成する(T)]が選択されているのがポイントのようです。自信度はあまり高くないですが・・・(汗)
ダイアログ右上の ? マークをクリックすると表示されるヘルプを読んでも何のことだかよく分かりませんでしたが、Microsoft のドキュメント「同期操作と非同期操作」を読むと、その中の「タスク ベースの非同期パターン」が相当するのではないかと思いました。
(Visual Studio 2010 Professional では[非同期操作の生成(G)]というのがありますが、デフォルトではチェックは入っていません。未確認ですが、.NET 4.5 以降でしか使用できない async / await キーワードを利用して使うものではなさそうです)
サービス参照の追加が完了すると、以下の画像の通り、同期版の GetData メソッドに加えて非同期版の GetDataAsync メソッドも生成されているのがインテリセンスの表示から分かります。
クライアントアプリからは、async / await キーワードを使って非同期版メソッドを呼び出せば、時間がかかる WCF サービスから応答が返ってくるまで UI がブロックされるということはなくなります。
それを試すには以下のようにします。
WCF サービス
まず、WCF のメソッドに 3 秒の待ち時間を入れます。これにより、同期の場合は呼び出し側の WPF アプリは 3 秒ブロックされますが、非同期の場合はブロックされないことを確認できます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Activation;
// このサービスをホストする ASP.NET アプリの web.config
// で aspNetCompatibilityEnabled="true" と設定されている
// 場合は以下の属性が必要
[AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed)]
public class Service : IService
{
public string GetData(int value)
{
// 非同期実行を確認するため追加
System.Threading.Thread.Sleep(3000);
return string.Format("You entered: {0}", value);
}
// CompositeType は IService.cs に自動生成されたクラス
public CompositeType
GetDataUsingDataContract(CompositeType composite)
{
// 非同期実行を確認するため追加
System.Threading.Thread.Sleep(3000);
if (composite == null)
{
throw new ArgumentNullException("composite");
}
if (composite.BoolValue)
{
composite.StringValue += "Suffix";
}
return composite;
}
}
WPF クライアントアプリ
呼び出し側のクライアントアプリでは以下のようにします。一番上の画像で、[同期呼び出し]ボタンのクリックイベントのハンドラが button_Click で、[非同期呼び出し]の方が button1_Click です。
// 同期
private void button_Click(object sender, RoutedEventArgs e)
{
var client = new TestService.ServiceClient();
var composite = new TestService.CompositeType();
label.Content = client.GetData(12345);
composite.BoolValue = checkBox.IsChecked == true;
composite.StringValue = textBox.Text;
composite = client.GetDataUsingDataContract(composite);
label1.Content = composite.StringValue;
}
// 非同期
// async void を async Task に変更すると、メソッドとデリゲート
// 型との間に互換性が無いというエラーになる
private async void button1_Click(object sender,
RoutedEventArgs e)
{
var client = new TestService.ServiceClient();
var composite = new TestService.CompositeType();
label.Content = await client.GetDataAsync(12345);
composite.BoolValue = checkBox.IsChecked == true;
composite.StringValue = textBox.Text;
composite =
await client.GetDataUsingDataContractAsync(composite);
label1.Content = composite.StringValue;
}
[同期呼び出し]ボタンをクリックすると上の画像のアプリは WCF サービスから応答が返ってくるまで操作できませんが、[非同期呼び出し]ボタンクリックなら操作可能で応答も期待通り返ってくることが確認できました。こんな簡単なことでホントにいいのか・・・という不安はありますが。(汗)
あと、Microsoft の文書「非同期プログラミングのベストプラクティス」には「async void メソッドよりも async Task メソッドを利用する」ということが書いてありますが、それはできなかったです(上のコードのコメントに書いたようにエラーになります)。解決方法があるのかどうかわかりませんが、分かったら追記することにします。