2013-06-17 33 views
10

thisソースからの非同期Web開発の概念を研究して、概念を証明するためのサンプルアプリケーションを作成しました。ASP.NET C#5 Async&Awaitを使用した非同期Webアプリケーション

このソリューションは、2つのASP.NET Web APIアプリケーションで構成されています。最初のものはシミュレートされた遅いエンドポイントです。それは、リストの学生と呼ばれるカスタムクラスを返す前に1000msのを待ち:ここ

public IEnumerable<Student> Get() 
    { 
     Thread.Sleep(1000); 
     return new List<Student> { new Student { Name = @"Paul" }, new Student { Name = @"Steve" }, new Student { Name = @"Dave" }, new Student { Name = @"Sue" } }; 
    } 

はStudentクラスです:

public class Student 
{ 
    public string Name { get; set; } 
} 

このエンドポイントは、ローカルホスト上のIIS 7でホストされている:4002。

第二のアプリケーションコンタクト第2のエンドポイント、1つの同期、他の非同期使用:

public IEnumerable<Student> Get() { 
     var proxy = WebRequest.Create(@"http://localhost:4002/api/values"); 

     var response = proxy.GetResponse(); 
     var reader = new StreamReader(response.GetResponseStream()); 

     return JsonConvert.DeserializeObject<IEnumerable<Student>>(reader.ReadToEnd()); 
    } 

    public async Task<IEnumerable<Student>> Get(int id) { 
     var proxy = new HttpClient(); 
     var getStudents = proxy.GetStreamAsync(@"http://localhost:4002/api/values"); 

     var stream = await getStudents; 
     var reader = new StreamReader(stream); 

     return JsonConvert.DeserializeObject<IEnumerable<Student>>(reader.ReadToEnd()); 
    } 

これは、ローカルホスト上のIIS 7でホストされている:4001。

両方のエンドポイントが正常に動作し、 1秒。上記の13:25のリンクの動画に基づいて、非同期メソッドはスレッドを解放して競合を最小限に抑える必要があります。

Apache Benchを使用してアプリケーションのパフォーマンステストを実行しています。ここでは、同期メソッドの応答時間は、10回の同時要求である:

Synchronous Results

これは私が期待通りくらいです。より多くの同時接続は競合を増やし、応答時間を延長します。しかし、ここでは非同期の応答時間は以下のとおりです。

Asynchronous Results

あなたが見ることができるように、まだいくつかの競合があるようです。私は、平均応答時間がより均衡すると予想していました。両方のエンドポイントで同時リクエスト数50回のテストを実行しても、同様の結果が得られます。

これに基づいて、非同期メソッドのオーバーヘッドを考慮せずに、非同期メソッドと同期メソッドの速度が多少なりとも同じであるように見えますが、非同期メソッドはスレッドをThreadPoolに戻してください。私はコメントや説明を歓迎する、ありがとう。

+0

最も大きな変化は、メソッドがデータベースなどの外部ソースに依存している場合です。 – JustAnotherUserYouMayKnow

+0

ありがとうございます、これは私がシミュレートしたものですが、長時間実行されているリクエストの場合に当てはまると思います。非同期フレームワークがCPUバインディングとは対照的にIOベースのリクエストに最適化されていることをお勧めしますか?コードを変更してデータベースリクエストを追加します。 –

+1

async-frameworkのポイントは、レスポンスを待たなければならないときにスレッドを解放することです。その間、他の作業も処理できます。これは、大部分のIOベースの要求の場合です。データベースから大量のデータを要求すると、その間にスレッドは何をすべきですか?データが戻ってくるまで待ってください...一方、スレッドプールに戻り、データが戻ってくるまで他の作業をしますか? – JustAnotherUserYouMayKnow

答えて

8

あなたがテストしていると思っているものをテストしていない可能性は非常に高いと思います。私が集めることから、タイミングを比較しスレッドのインジェクションを推論することで、スレッドプールに戻ってリリースを検出しようとしています。

一方、.NET 4.5のスレッドプールのデフォルト設定は非常に高いです。あなたは10または100の同時リクエストでそれらをヒットするつもりはありません。

1秒後に戻って、テストしたいことを考えてみましょう。非同期メソッドはスレッドをスレッドプールに戻しますか?

私はこれを実証するために示したデモがあります。デモ用の負荷テストを作成したくないので、私はちょっとしたトリックを引っ張りました。人工的にスレッドプールをより合理的な値に制限しています。

これを実行したら、テストは非常に簡単です。多くの同時接続を実行してから、多くのと1つのを実行します。同期実装では、最後の実装を開始する前に完了するまで待つ必要がありますが、非同期実装ではすべて開始することができます。サーバー側では

、最初にシステム内のプロセッサの数にスレッドプールのスレッドを制限:

public class ValuesController : ApiController 
{ 
    // Synchronous 
    public IEnumerable<string> Get() 
    { 
     Thread.Sleep(1000); 
     return new string[] { "value1", "value2" }; 
    } 

    // Asynchronous 
    public async Task<IEnumerable<string>> Get(int id) 
    { 
     await Task.Delay(1000); 
     return new string[] { "value1", "value2" }; 
    } 
} 

そして最後にクライアント:

protected void Application_Start() 
{ 
    int workerThreads, ioThreads; 
    ThreadPool.GetMaxThreads(out workerThreads, out ioThreads); 
    ThreadPool.SetMaxThreads(Environment.ProcessorCount, ioThreads); 
    ... 
} 

は、次に同期および非同期の実装を行いますテストコード:

static void Main(string[] args) 
{ 
    try 
    { 
     MainAsync().Wait(); 
    } 
    catch (Exception ex) 
    { 
     Console.WriteLine(ex); 
    } 

    Console.ReadKey(); 
} 

static async Task MainAsync() 
{ 
    ServicePointManager.DefaultConnectionLimit = int.MaxValue; 

    var sw = new Stopwatch(); 
    var client = new HttpClient(); 
    var connections = Environment.ProcessorCount; 
    var url = "http://localhost:35697/api/values/"; 

    await client.GetStringAsync(url); // warmup 
    sw.Start(); 
    await Task.WhenAll(Enumerable.Range(0, connections).Select(i => client.GetStringAsync(url))); 
    sw.Stop(); 
    Console.WriteLine("Synchronous time for " + connections + " connections: " + sw.Elapsed); 

    connections = Environment.ProcessorCount + 1; 

    await client.GetStringAsync(url); // warmup 
    sw.Restart(); 
    await Task.WhenAll(Enumerable.Range(0, connections).Select(i => client.GetStringAsync(url))); 
    sw.Stop(); 
    Console.WriteLine("Synchronous time for " + connections + " connections: " + sw.Elapsed); 

    url += "13"; 
    connections = Environment.ProcessorCount; 

    await client.GetStringAsync(url); // warmup 
    sw.Restart(); 
    await Task.WhenAll(Enumerable.Range(0, connections).Select(i => client.GetStringAsync(url))); 
    sw.Stop(); 
    Console.WriteLine("Asynchronous time for " + connections + " connections: " + sw.Elapsed); 

    connections = Environment.ProcessorCount + 1; 

    await client.GetStringAsync(url); // warmup 
    sw.Restart(); 
    await Task.WhenAll(Enumerable.Range(0, connections).Select(i => client.GetStringAsync(url))); 
    sw.Stop(); 
    Console.WriteLine("Asynchronous time for " + connections + " connections: " + sw.Elapsed); 
} 

my(8-logical-core)machはっきり非同期メソッドは、スレッド・プールにそのスレッドを返していることを示して

Synchronous time for 8 connections: 00:00:01.0194025 
Synchronous time for 9 connections: 00:00:02.0362007 
Asynchronous time for 8 connections: 00:00:01.0413737 
Asynchronous time for 9 connections: 00:00:01.0238674 

:伊根は、私はこのような出力を参照してください。

+0

スティーブン、素晴らしい説明に感謝します。ちなみに、IISが非同期要求中にThreadPoolにThreadを返すと、コールバックを管理するステートマシンはどのスレッドで実行されますか? IISによってホストされていますか?その場合、ThreadPoolを活用するのですか、それともIIS以外のOSスレッドでも実行されますか? –

+0

デフォルトでは、 'async'メソッドはリクエストコンテキストで再開します。実際には、ASP.NETは利用可能なスレッド(スレッドプールから)を取得し、そのスレッドはメソッドを再開する前に要求コンテキストに入ります。 –

+0

ありがとうございます。待っているメソッドが返るまで、スレッドはプールに戻されることを理解しています。待機しているリクエストを監視するステートマシンに関しては、それはThreadPool上のASP.NETのコンテキストで実行されますか?それとも他の場所でホストされていますか? –

関連する問題