親愛なるハラジテルの場合、それは面白いです-カットをお願いします!
「 awaitキーワードを使用した非同期プログラミングの観点から、C#5.0の新機能を紹介します。 特にASP.NET MVC 4 Webアプリケーションの場合。」
私はしばらくの間Visual Studio Async CTPをいじくり回しましたが、今ではそれがどのように機能し、賢く使用するかについて十分な考えを持っています。 そして、それは私がそれについて書くことができることを意味します。
ほとんどのソフトウェアの主な問題は、長い実行時間を必要とする操作です。 ユーザーはそのような操作を開始します-そしてそれは完了するまでプログラムのメインスレッドをブロックします。 その結果、不便なソフトウェアユーザーと不満のある顧客を獲得しています。 私たち一人一人がそのようなプログラムに出会ったと確信しています!
非同期プログラミングは、新しいパラダイムとはほど遠いもので、.NET 1.0の時代にはすでにありました。 私は3年前にプログラミングにあまり興味がなかったので、この概念は今日ではまったく新しいものです。 私が読んでいる限り、この概念は非同期プログラミングの.NETの観点から大きく進化しており、私には思えるように、今では最高の段階にあります。
一方で、非同期プログラミングで混乱するのは非常に簡単です。既に何度か起こったことです。 私は行ったり来たりして、このことがどのように機能し、どこで使用するかについて多くのことを考えました。 そして、最終的には正しく理解したと信じたい。 一方、非同期プログラミングでは、Webアプリケーションで使用すると混乱しやすくなります。作業の結果をすぐに出力できるUIストリームがないためです。 非同期かどうかにかかわらず、ユーザーはしばらく待つ必要があります。 これが、非同期プログラミングがWebアプリケーションにとって望ましくない理由です。 そして、あなたもそう思うなら(そして私自身もそう思っていた)、あなたはまだ何も理解していない!
操作が同期的で時間がかかる場合、メインスレッドを完了するまでブロックする以外に選択肢はありません。 また、同じ操作を非同期で行う場合、操作を開始して何かを続行し、操作が既に完了している場合にのみ操作の結果に戻ることを意味します。 操作の開始から終了までの間、スレッドは自由であり、他のことを行うことができます。 ほとんどの人はここで混乱すると思います。 追加のスレッドを作成することはオーバーヘッドの問題であり、問題につながる可能性がありますが、これはすでに過去にあるように思えます。 .NET 4.0では、スレッド数の制限が大幅に増加しましたが、これはどこでも多くのスレッドを作成する必要があるという意味ではありません-少なくとも今は次のスレッドを作成するときに心配する必要はありません。 非同期プログラミングに関する多くの論争があります:
- データベースへの呼び出しは非同期ですか?
- データベースアクセスを非同期にする必要があります(パート2)。
- ASP.NET MVCの非同期操作は、.NET 4プール(ThreadPool)のスレッドをどのように使用しますか?
- C#AsyncCTPでExecuteReaderAsyncを使用することの欠点は何ですか?
これらすべての質問に対する正確な答えはわかりませんが、ここにいくつかの例を示します。
- Windowsランタイム(WinRT)は、優れた非同期サポートで設計されました。 40ミリ秒より長い時間がかかるすべて(ネットワーク、ファイルシステム、つまりすべてのメインI / Oに関連する操作)-これらはすべて非同期でなければなりません!
- ASP.NET Web APIを使用すると、いくつかの形式でサービスのデータを送信できます。 唯一注意すべきことは、実質的に同期呼び出しがないことです。 文字通り(比fig的ではありません)-いくつかのメソッドの同期呼び出しはありません!
そうは言っても、それは何らかの形で非同期性を活用するときです。 それで問題は何ですか? そして問題は、この非同期モデルがどのように機能するかです。 Anders Halesbergが言うように、このソフトウェアモデルはコードを裏返しにしています。 例外処理を伴う非同期操作のネストされた呼び出しを行うのが非常に難しいことは言うまでもありません。
Visual Studio Async CTPとその仕組み
C#5.0では、同期に非常によく似た新しい非同期プログラミングモデルが作成されます。 それまでの間、開発者は現在のバージョンのCTPをリリースしており、Go-Liveライセンスで利用できます。 AsyncCTP仕様からの引用は次のとおりです。
" 非同期関数は、非同期呼び出しの記述を容易にするC#の新機能です。関数がawaitキーワードを使用して式に到達し、実行を開始すると、関数の残りの部分が操作の自動継続として再構築され、非同期関数から即座に戻ります。関数自体は引き続き実行されます。つまり、プログラマーではなく言語自体がコード実行の順序を構築するタスクを引き受けます。 クロノ・コードは、論理的な構造を獲得します。」
非同期関数は、 非同期修飾子でマークされたクラスメソッドまたは匿名関数です。 Taskクラスのオブジェクトと、任意のタイプTの Task <T>の両方を返すことができます。そのような関数では、 待機することができます。 asyncとマークされた関数がvoid (これも有効)を返す場合、 awaitは使用できません。 しかし、他方では、 Tとして任意の型を使用できます。
ASP.NET MVC 4およびC#非同期機能
ASP.NET MVCアプリケーションで非同期を適切に使用すると、パフォーマンスに非常に良い影響を与える可能性があります。 信じられないかもしれませんが、そうです。 次に、その方法を示します。
それでは、なぜ私たちはどこでもなく、すべてを非同期に行うのですか? そして、それは非常に難しく、エラーに満ちており、その後アプリケーションを維持するのが最終的に難しいからです。 しかし、新しい非同期モデルでは、これはすぐに変わります。
ASP.NET MVC 4では、非同期プログラミングが大きく変わりました。 ASP.NET MVC 3では、コントローラーをAsyncControllerから継承し、特定のパターンに準拠する必要があることをご存知かもしれません。 これについては、記事「 ASP.NET MVCで非同期コントローラーを使用する 」 を参照してください。
ASP.NET MVC 4では、AsyncControllerを使用する必要がなくなりました。async キーワードでコントローラーをマークし、 TaskまたはTask <T>クラス( Tは通常ActionResult )のオブジェクトを返すだけです。
1つのアプリケーションで2つの例を同時に組み合わせてみて、同じアクション(同期的および非同期的)をそれぞれ実行しようとしました。 私もストレステストを行いましたが、その結果は衝撃的でした! それでは、コードを見てみましょう。
まず、新しいASP.NET Web APIを使用して最も単純なRESTサービスを作成しました。 コレクションをメモリに保存する最も単純なモデル(代わりにデータベースを使用できます):
public class Car { public string Make; public string Model; public int Year; public int Doors; public string Colour; public float Price; public int Mileage; } public class CarService { public List<Car> GetCars() { List<Car> Cars = new List<Car> { new Car { Make="Audi", Model="A4", Year=1995, Doors=4, Colour="Red", Price=2995f, Mileage=122458 }, new Car { Make="Ford", Model="Focus", Year=2002, Doors=5, Colour="Black", Price=3250f, Mileage=68500 }, new Car { Make="BMW", Model="5 Series", Year=2006, Doors=4, Colour="Grey", Price=24950f, Mileage=19500 } //This keeps going like that }; return Cars; } }
そしてサービスコード:
public class CarsController : ApiController { public IEnumerable<Car> Get() { var service = new CarService(); return service.GetCars(); } }
だから、私にはサービスがあります。 次に、サービスからデータを受信して表示するWebアプリケーションを作成します。 これを行うために、データを受信してデータをデシリアライズするサービスクラスを作成しました。非同期呼び出しには新しいHttpClientを使用し、同期呼び出しには通常のWebClientを使用しました 。 私もJson.NETを使用しました :
public class CarRESTService { readonly string uri = "http://localhost:2236/api/cars"; public List<Car> GetCars() { using (WebClient webClient = new WebClient()) { return JsonConvert.DeserializeObject<List<Car>> ( webClient.DownloadString(uri) ); } } public async Task<List<Car>> GetCarsAsync() { using (HttpClient httpClient = new HttpClient()) { return JsonConvert.DeserializeObject<List<Car>> ( await httpClient.GetStringAsync(uri) ); } } }
GetCarsメソッドについて言うことは何もありません-それは簡単です。 しかし、 GetCarsAsyncについては、次のことが言えます。
- asyncキーワードでマークされており、何らかの非同期コードがあることを示しています。
- Task <string>を返すHttpClient.GetStringAsyncの前にawaitキーワードを使用しました。 そして、通常の文字列として使用できます-awaitキーワードを使用すると、これを実行できます。
最後に、コントローラーを示します。
public class HomeController : Controller { private CarRESTService service = new CarRESTService(); public async Task<ActionResult> Index() { return View("index", await service.GetCarsAsync()); } public ActionResult IndexSync() { return View("index", service.GetCars()); } }
そこで、 タスク<ActionResult>を返す非同期関数であるIndexと、通常の同期呼び出しであるIndexSyncを取得しました 。 アドレス/ home / indexまたは/ home / indexsyncにアクセスすると 、ページを開く速度に大きな違いは見られません-それはほぼ同じです。
これらのメソッドの動作の違いを測定するために、MS Visual Studio 2010 Ultimateで負荷テストを作成しました。 これらの2つのページを2分以内に開きます。50ユーザーの負荷で開始し、5秒ごとに20ユーザーずつ徐々に増やしていきます。 最大制限は500ユーザーです。 結果を見る(クリック可能):
同期バージョンの場合、応答時間は11.2秒、非同期バージョンの場合は3.65秒であることがわかります。 その違いは明らかだと思います。
その瞬間から、私は「40ms(ネットワーク、ファイルシステム、つまりすべてのメインI / Oに関連する操作)よりも長い時間がかかるすべて-これはすべて非同期でなければならない!」という概念の支持者になりました。