非同期ページとは
ASP.NET 4.5が含まれる.NET Frameworkの最大の新機能といえば、async/awaitキーワードを使った非同期処理のコードの簡素化です。ASP.NET 4.5 Webフォームでもその機能を活用できる局面があります。それが「非同期ページ」です。
非同期ページと聞くと、Webアプリケーションの処理を実行すると、サーバーサイドの処理を非同期で実行後にすぐにクライアントサイドに処理を戻し、サーバーサイド処理が終わったら改めてレスポンスが返ってくる、といったものを想像するかもしれません(図5)。しかし、実際の非同期ページの動作は、そうではありません。
非同期ページの正しい動作とは、「サーバーサイドの一部の処理を非同期に、並列して実行する」イメージです(図6)。したがって、クライアントサイドから見ても、同期ページと非同期ページの違いはわかりません。
それでは、何のためにこの機能があるのでしょうか。それは、比較的多くのリクエストをさばかなければならないアプリケーションでは、同期処理だけだとASP.NETのに割り当てられたスレッドが枯渇してしまう恐れがあるためです。
非同期ページでは、一部の処理を別スレッドに「非同期」に任せて並列処理を行うことにより、サーバーのリソースを有効に活用できます。このあたりの詳しい話は、次の記事を参照してください。
ちなみに、「非同期ページ」という仕組み自体は、ASP.NET 2.0ですでに導入されていました。ただ、その必要性や実装の煩雑さから、それほどメジャーとは言えない機能の一つでした。しかし、.NET 4.5のasync/await構文の力を使えるようになったことで、ASP.NET 2.0の頃に比べて、比較的容易に導入することが可能となりました。
なお、勘違いしてはいけないのが、async/await構文で「非同期処理」自体が簡単になる、というわけではないということです。非同期処理を行うには、十分な検討と設計が必要なことは覚えておいてください。
補足:非同期処理の基本とasync/await
非同期処理の基本は、アプリケーションの一部の動作を、他人に行わせるという考えです。具体的な例で言えば、メインのスレッドとは別のスレッドに処理を行わせる、ということになります。
別スレッドに非同期で処理を呼び出すと、メインスレッドはそのまま処理を続行、もしくは終了しますが、別スレッドでは並行して処理が行われ、終了した段階でその結果をメインスレッドに通知します。そして通知を受けたメインスレッドでは、別スレッドで行った処理の結果を取得し、その結果を使用する処理を実行します。
こういったスレッドを使った非同期処理をシンプルなAPIで実現するため、.NET 4.0で導入されたのがTaskクラスです。Taskクラスを使った非同期処理は、TaskクラスのContinueWithメソッドを使うことで、非同期処理結果をTask.Resultプロパティから取り出せます(リスト3)。
using System; using System.Threading; using System.Threading.Tasks; namespace TaskSample { class Program { static void Main(string[] args) { Console.WriteLine("メイン処理:スレッドID={0}", Thread.CurrentThread.ManagedThreadId); var a = 2; // 非同期処理を行うタスクオブジェクトを取得する var task = Square(a); // 非同期処理が終わった後に継続して行う処理を指定する task.ContinueWith(t => // tはTask型 { var result = t.Result; Console.WriteLine("結果:" + result); }); Console.WriteLine("処理中..."); Console.ReadKey(); } static Task<int> Square(int value) { // 非同期で処理を行うためのTaskを実行してそのオブジェクトを返す return Task.Run(() => { Thread.Sleep(1000); Console.WriteLine("非同期処理:スレッドID={0}", Thread.CurrentThread.ManagedThreadId); return value * value; }); } } }
実行結果は次のようになり、メインスレッドと非同期処理が別スレッドで行われていることが確認できます(スレッドIDは実行環境によって異なります)。
メイン処理:スレッドID=1 処理中... 非同期処理:スレッドID=3 結果:4
Taskクラスを使った非同期処理は、.NET 4.5で導入されたasync/await構文を使うと、同期処理とほぼ同じ記述順でロジックを記述できます(リスト4)。これにより、コードの読みやすさを維持したまま、非同期処理を手軽に扱えるようになるのです。
using System; using System.Threading; using System.Threading.Tasks; namespace AsyncAwaitSample { class Program { static void Main(string[] args) { Console.WriteLine("メイン処理:スレッドID={0}", Thread.CurrentThread.ManagedThreadId); var a = 2; // 非同期処理を行うタスクオブジェクトを取得する var task = Square(a); Console.WriteLine("処理中..."); Console.WriteLine("結果:" + task.Result); Console.ReadKey(); } static async Task<int> Square(int value) { // 非同期で処理を行うためのTaskを実行し、終了を待たずにオブジェクトを返す return await Task.Run(() => { Thread.Sleep(1000); Console.WriteLine("非同期処理:スレッドID={0}", Thread.CurrentThread.ManagedThreadId); return value * value; }); } } }
async/await構文は、まず非同期処理を呼び出すメソッドに「async」でマークを付けます。そして、非同期処理を呼び出している箇所に「await」を指定することで、その終了を待たずに一度処理を呼び出し元に返すようになります。つまり、呼び出し元でTask.Resultプロパティを参照している箇所が、非同期処理終了後に実行されるようになります。その証拠に、コード上ではSquareメソッドが"処理中..."を出力している箇所より前に記載されていますが、実行するとリスト3と同じく"処理中..."の後で"非同期処理:スレッドID=~"が出力されます。