Visual Studio 2012に含まれるC# 5.0とVisual Basic 11.0には、非同期処理を容易にするために「async/await」という言語機能*8が導入されている。応用編では、WinRTの非同期メソッドをasync/awaitで記述することを目的としている。いきなりコードを提示したいところだが、Windowsストア・アプリのプロジェクトと違って、準備が必要になる。では、具体的な作業を使って説明していく。
*8 正確には、コンパイラに対する特別な指示を意味するキーワードとなる。この意味では、「コンパイラ・ディレクティブ」と呼んでもよいと考えるが、より現代風に呼ぶのであれば「非同期用のDSL(ドメイン固有言語)」と呼ぶのがふさわしいはずだ。
[ソリューション エクスプローラー]を使って[参照設定]−[参照の追加]をクリックして、次に示すライブラリへの参照設定を行う(次の画面はその実施例)。
System.Threading.Tasks.dllファイルへの参照を追加したら、次にSystem.Runtime.WindowsRuntime.dllファイルへの参照を追加する。
これでaswync/awaitを使用する準備の完了である。次は、具体的なコードを説明していく。
System.Threading.TasksアセンブリとSystem.Runtime.WindowsRuntimeアセンブリへの参照を追加して、コンソール・アプリでasync/awaitを使用する方法は、ドキュメント化されていない方法である。公開されているサンプルには、非同期メソッドを使用していても同期的な処理を行うコードしかない。この意味において、ここで説明している方法はハック行為の一種と考えるべきである。筆者がこの方法に気が付いたのは、Windowsストア・アプリにおいて、.NET FrameworkのTaskクラスとWinRTの非同期基盤がどのように連携しているかを調べたためだ。もちろん、マイクロソフトの公式サポートがあるわけではない。この点を踏まえて、あくまでも自己責任で利用してほしい。
次に示すのが、async/awaitを使って書き直したコードになる。名前空間の使用は、基本的な使い方で説明したものと同じであることから割愛している(実行結果は、基本的な使い方で示したものと同じである)。
static void Main(string[] args)
{
var task = GetDeviceInformation(); // …… (1)
while (!task.IsCompleted ) // …… (2)
{
System.Threading.Thread.Sleep(500);
}
Console.ReadKey();
}
static async Task GetDeviceInformation() // …… (3)
{
DeviceInformationCollection deviceCollection;
deviceCollection = await DeviceInformation.FindAllAsync(); // …… (4)
foreach (DeviceInformation device in deviceCollection) // …… (5)
{
Console.WriteLine("Name={0}, ID={1}", device.Name, device.Id);
}
}
Sub Main()
Dim task = New Task(Async Sub() Await GetDeviceInformation()) ' …… (1)
task.Start()
While (Not task.IsCompleted) ' …… (2)
System.Threading.Thread.Sleep(500)
End While
Console.WriteLine("Dummy")
Console.ReadKey()
End Sub
Async Function GetDeviceInformation() As Task ' …… (3)
Dim deviceCollection As DeviceInformationCollection
deviceCollection = Await DeviceInformation.FindAllAsync() ' …… (4)
For Each device As DeviceInformation In deviceCollection ' …… (5)
Console.WriteLine("Name={0}, ID={1}", device.Name, device.Id)
Next
End Function
このコードでは、async/awaitを使用するために、GetDeviceInformationメソッドを定義しており、このメソッド内でFindAllAsyncメソッドを待機呼び出し(await)している。これは、メソッドに記述するasyncキーワードをエントリ・ポイントとなるメイン・メソッドに記述できないからだ*9。最も基本的な方法で説明した同期コードと比較すれば、GetDeviceInformationメソッドのメリットは明らかなはずである。非同期メソッドをあたかも同期コードのように記述できており、コードの保守容易性が向上するからだ*10。
*9 asyncキーワードには、エントリ・ポイント以外にも、クラスのコンストラクタに記述できないという制約もある。
*10 「保守容易性」という言葉は、特別な知識を必要としないで保守できるという意味で使用している。
リスト5では、リスト4までのマジック・コードと大きく変化している箇所があることに気が付いただろうか。特にVisual Basicで顕著になっている。それは、GetDeviceInformationメソッドの呼び出し箇所である。Visual Basic版では、Taskクラスのインスタンスを新しく作成し、Task.StartメソッドでTaskを開始するようになっている。この理由は、GetDeviceInformationメソッドを呼び出しただけではTaskが開始されなかったからである。理由は調べていないが、今回のような作り方ではVisual BasicコンパイラがC#コンパイラと異なる非同期の実装コードを出力しているためかもしれない。さらにTaskクラスのインスタンスを作成した副次効果として、Taskの終了を待った後に「Console.WriteLine("Dummy")」というコードだけで筆者の環境では結果の出力を得られたことだ。このように非同期処理を同期処理に押し込める方法では、さまざまなマジック・コードが必要となる。しかも、マジック・コードは環境依存でもある。この点を踏まえてコードを書く限りは、大きな問題を感じることはないはずだ
いかがだっただろうか。Windows 8というOS固有のWinRTをデスクトップ・アプリから使用できることを、十分に感じられたのではないだろうか。必要なことは、できないと考えることではなく、できる可能性はないかと身近なことから調べていく努力だ。簡単にあきらめることなく、プログラミング(自分にとってのハック)を楽しんでほしい。最後に次の言葉で、本稿を終了する。
Enjoy Programming! Let’s Hack!
Copyright© Digital Advantage Corp. All Rights Reserved.