web-dev-qa-db-ja.com

ASP.NET MVCの非同期操作で.NET 4のThreadPoolのスレッドを使用する

この質問の後、ASP.NET MVCで非同期操作を使用するときに快適になります。それで、私はそれについて2つのブログ記事を書きました:

ASP.NET MVCでの非同期操作について、あまりにも多くの誤解があります。

私はいつもこの文を聞きます:操作が非同期に実行される場合、アプリケーションはより良く拡張できます

そして、私はこの種の文をよく聞きました:大量のトラフィックがある場合は、クエリを非同期に実行しない方が良いかもしれません2 1つの要求を処理するための余分なスレッドは、他の着信要求からリソースを奪います。

これらの2つの文は矛盾していると思います。

ASP.NETでのthreadpoolの動作に関する情報はあまりありませんが、threadpoolのスレッドサイズには制限があることは知っています。したがって、2番目の文はこの問題に関連する必要があります。

そして、ASP.NET MVCの非同期操作が.NET 4のThreadPoolのスレッドを使用するかどうかを知りたいのですが?

たとえば、AsyncControllerを実装する場合、アプリはどのように構成されますか?大量のトラフィックが発生する場合、AsyncControllerを実装することをお勧めしますか?

私の目の前でこの黒いカーテンを取り除いて、ASP.NET MVC 3(NET 4)での非同期性についての取引を説明できる人はいますか?

編集:

以下のドキュメントを何百回も読んでおり、主な取引を理解していますが、そこには一貫性のないコメントが多すぎるため、まだ混乱しています。

ASP.NET MVCで非同期コントローラーを使用

編集:

以下のようなコントローラーアクションがあると仮定しましょう(AsyncControllerの実装ではありません):

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

ここにあるように、私は手術を発動し、それを忘れます。その後、完了するのを待たずにすぐに戻ります。

この場合、これはスレッドプールのスレッドを使用する必要がありますか?もしそうなら、それが完了した後、そのスレッドはどうなりますか? GCは、完了してすぐにクリーンアップされますか?

編集:

@Darinの答えとして、データベースと通信する非同期コードのサンプルを以下に示します。

public class FooController : AsyncController {

    //EF 4.2 DbContext instance
    MyContext _context = new MyContext();

    public void IndexAsync() { 

        AsyncManager.OutstandingOperations.Increment(3);

        Task<IEnumerable<Foo>>.Factory.StartNew(() => { 

           return 
                _context.Foos;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foos"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<Bars>>.Factory.StartNew(() => { 

           return 
                _context.Bars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["bars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<FooBar>>.Factory.StartNew(() => { 

           return 
                _context.FooBars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foobars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });
    }

    public ViewResult IndexCompleted(
        IEnumerable<Foo> foos, 
        IEnumerable<Bar> bars,
        IEnumerable<FooBar> foobars) {

        //Do the regular stuff and return

    }
}
157
tugberk

ここに 優秀な記事 があります。ASP.NETの非同期処理(非同期コントローラーが基本的に表すもの)をよりよく理解するために読むことをお勧めします。

最初に標準の同期アクションを考えてみましょう。

public ActionResult Index()
{
    // some processing
    return View();
}

このアクションに対して要求が行われると、スレッドがスレッドプールから取得され、このスレッドでこのアクションの本体が実行されます。したがって、このアクション内の処理が遅い場合、処理全体でこのスレッドをブロックしているため、このスレッドを他のリクエストの処理に再利用することはできません。要求の実行が終了すると、スレッドはスレッドプールに返されます。

次に、非同期パターンの例を見てみましょう。

public void IndexAsync()
{
    // perform some processing
}

public ActionResult IndexCompleted(object result)
{
    return View();
}

要求がインデックスアクションに送信されると、スレッドプールからスレッドが取得され、IndexAsyncメソッドの本体が実行されます。このメソッドの本体の実行が完了すると、スレッドはスレッドプールに返されます。次に、標準のAsyncManager.OutstandingOperationsを使用して、非同期操作の完了を通知すると、スレッドプールから別のスレッドが引き出され、IndexCompletedアクションの本体が実行され、結果がクライアント。

したがって、このパターンでわかるのは、単一のクライアントHTTPリクエストが2つの異なるスレッドによって実行される可能性があるということです。

興味深い部分はIndexAsyncメソッド内で発生します。内部にブロッキング操作がある場合、ワーカースレッドをブロックしているため、非同期コントローラーの目的全体を無駄にしています(このアクションの本体はスレッドプールから引き出されたスレッドで実行されることに注意してください)。

非同期コントローラを実際に活用できるのはいつでしょうか?

私見では、I/Oを集中的に使用する操作(データベースやリモートサービスへのネットワーク呼び出しなど)がある場合に最も多くを得ることができます。 CPUを集中的に使用する操作を行っている場合、非同期アクションではあまりメリットがありません。

では、なぜI/O集中型の操作から利益を得ることができるのでしょうか? I/O Completion Ports を使用できるためです。 IOCPは、操作全体の実行中にサーバー上のスレッドまたはリソースを消費しないため、非常に強力です。

彼らはどのように機能しますか?

WebClient.DownloadStringAsync メソッドを使用して、リモートWebページのコンテンツをダウンロードするとします。このメソッドを呼び出すと、オペレーティングシステム内にIOCPが登録され、すぐに戻ります。リクエスト全体の処理中、サーバーでスレッドは消費されません。すべてがリモートサーバーで発生します。これには多くの時間がかかりますが、ワーカースレッドを危険にさらすことはないので気にしません。応答が受信されると、IOCPが通知され、スレッドプールからスレッドが取得され、このスレッドでコールバックが実行されます。しかし、ご覧のとおり、プロセス全体を通して、スレッドを独占していません。

FileStream.BeginRead、SqlCommand.BeginExecute、...などのメソッドでも同じことが言えます。

複数のデータベース呼び出しの並列化はどうですか? 4つのブロッキングデータベース呼び出しを順番に実行する同期コントローラーアクションがあったとします。各データベース呼び出しが200ミリ秒かかる場合、コントローラーアクションの実行に約800ミリ秒かかることを計算するのは簡単です。

これらの呼び出しを順番に実行する必要がない場合、それらを並列化するとパフォーマンスが向上しますか?

それは大きな質問であり、答えるのは簡単ではありません。たぶんそうだけどたぶん違う。これらのデータベース呼び出しの実装方法に完全に依存します。前述のように非同期コントローラーとI/O完了ポートを使用すると、ワーカースレッドを独占しないため、このコントローラーアクションと他のアクションのパフォーマンスも向上します。

一方、それらの実装が不十分な場合(スレッドプールのスレッドで実行されるブロッキングデータベース呼び出しで)、基本的にこのアクションの実行の合計時間は約200ミリ秒に短縮されますが、4つのワーカースレッドを消費するため、他のリクエストのパフォーマンスが低下した可能性があり、それらのリクエストを処理するためのプール内のスレッドが欠落しているために飢えている可能性があります。

したがって、非常に困難であり、アプリケーションで広範なテストを実行する準備ができていない場合は、非同期コントローラーを実装しないでください。利益よりも多くの損害を与える可能性があります。実装する理由がある場合にのみ実装してください。たとえば、標準の同期コントローラアクションがアプリケーションのボトルネックであることを特定しました(もちろん、広範な負荷テストと測定を実行した後)。

それでは、例を考えてみましょう。

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

インデックスアクションの要求を受信すると、スレッドプールからスレッドが引き出されてそのボディが実行されますが、ボディは TPL を使用して新しいタスクのみをスケジュールします。したがって、アクションの実行は終了し、スレッドはスレッドプールに返されます。それを除いて、 TPL はスレッドプールのスレッドを使用して処理を実行します。そのため、元のスレッドがスレッドプールに返された場合でも、タスクの本体を実行するためにこのプールから別のスレッドを引き出しています。したがって、貴重なプールから2つのスレッドが危険にさらされています。

次に、次のことを考えてみましょう。

public ViewResult Index() { 

    new Thread(() => { 
        //Do an advanced looging here which takes a while
    }).Start();

    return View();
}

この場合、手動でスレッドを生成しています。この場合、インデックスアクションの本体の実行には少し時間がかかる場合があります(新しいスレッドの生成は既存のプールからスレッドを描画するよりもコストがかかるため)。ただし、高度なロギング操作の実行は、プールの一部ではないスレッドで実行されます。したがって、別のリクエストを処理するために空きのままであるスレッドをプールから危険にさらすことはありません。

175
Darin Dimitrov

はい-すべてのスレッドはスレッドプールから来ます。 MVCアプリは既にマルチスレッド化されており、リクエストが着信すると、新しいスレッドがプールから取得され、リクエストの処理に使用されます。そのスレッドは、リクエストが完全に処理されて完了するまで(他のリクエストから)「ロック」されます。プールに利用可能なスレッドがない場合、リクエストは利用可能になるまで待機する必要があります。

非同期コントローラーがある場合、プールからスレッドを取得しますが、リクエストを処理している間、スレッドを放棄し、何かが発生するのを待っている間(およびそのスレッドを別のリクエストに渡すことができます)、元のリクエストにスレッドが必要な場合再び、プールから1つを取得します。

違いは、長時間実行される要求(スレッドが何かからの応答を待機している場合)が多数ある場合、プールからのスレッドが不足して基本的な要求も処理する可能性があることです。非同期コントローラーがある場合、スレッドはもうありませんが、待機しているスレッドはプールに返され、他の要求を処理できます。

A ほぼ実生活の例...バスに乗るようなものだと考えてください。乗車するのを待っている人が5人いて、最初に乗車し、支払いをして座ります(運転手がリクエストに応えました)。乗車します(ドライバーがリクエストを処理しています)が、お金が見つかりません。あなたがポケットを手探りするとき、運転手はあなたをあきらめ、次の2人を乗せます(彼らの要求に応えます)、あなたがお金を見つけると、運転手はあなたと再びやり始めます(あなたの要求を完了します)-5人目は待たなければなりませんあなたは終わりましたが、あなたが奉仕する途中である間に、3番目と4番目の人々が奉仕しました。これは、ドライバーがプールからの唯一のスレッドであり、乗客がリクエストであることを意味します。 2人のドライバーがいた場合にどのように機能するかを書くのは複雑すぎましたが、想像できます...

非同期コントローラーがなければ、あなたの後ろの乗客はお金を探している間年齢を待たなければならず、その間バスの運転手は仕事をしていません。

したがって、結論は、多くの人々が自分のお金がどこにあるかわからない場合(つまり、ドライバーが尋ねたものに応答するのに長い時間を必要とする場合)、非同期コントローラーはリクエストのスループットを助け、一部のプロセスを高速化することができます。 aysncコントローラーがなければ、全員が前の人が完全に対処されるまで待ちます。ただし、MVCでは1つのバスに多数のバスドライバーがあるため、非同期は自動的に選択されないことを忘れないでください。

48
K. Bob

ここには2つの概念があります。まず、コードを並行して実行して、より高速に実行したり、別のスレッドでコードをスケジュールして、ユーザーが待たされることを回避できます。あなたが持っていた例

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

2番目のカテゴリに属します。ユーザーはより高速な応答を取得しますが、同じ作業を行う必要があり、スレッドを処理する必要があるため、サーバー上の合計ワークロードが高くなります。

この別の例は次のとおりです。

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Make async web request to Twitter with WebClient.DownloadString()
    });

    Task.Factory.StartNew(() => { 
        //Make async web request to facebook with WebClient.DownloadString()
    });


    //wait for both to be ready and merge the results

    return View();
}

リクエストは並行して実行されるため、ユーザーはシリアルで実行される場合と同じくらい長く待つ必要はありません。ただし、スレッドを待機中に多くのスレッドでコードを実行するため、シリアルで実行した場合よりも多くのリソースを使用することに注意してください。

これは、クライアントのシナリオではまったく問題ありません。また、新しいタスクで同期の長時間実行されるコードをラップする(別のスレッドで実行する)ことも非常に一般的です。ただし、スレッドは全期間使用されます。高負荷のサーバーでは、実際により多くのリソースを使用するため、これが逆火になる可能性があります。 これは人々があなたに警告したものです

ただし、MVCの非同期コントローラーには別の目標があります。ここでのポイントは、スレッドが何もしないで座っていることを避けることです(これにより、スケーラビリティが損なわれる可能性があります)。呼び出すAPIが非同期メソッドを持っている場合にのみ問題になります。 WebClient.DowloadStringAsync()と同様です。

ポイントは、同じまたは新しいスレッドを取得してリクエストを終了するコールバックを呼び出すWebリクエストが終了するまで、スレッドを返して新しいリクエストを処理できるようにすることです。

非同期と並列の違いを理解してください。並列コードは、スレッドが座って結果を待つコードと考えてください。非同期コードは、コードが完了すると通知されるコードであり、そのコードで作業を取り戻すことができますが、その間にスレッドは他の作業を行うことができます。

10
Mikael Eliasson

アプリケーションcanは、操作が非同期で実行される場合はより適切にスケーリングできますが、追加の操作を処理するために利用可能なリソースがある場合のみです。

非同期操作は、既存のアクションが進行中のため、アクションをブロックしないようにします。 ASP.NETには、複数の要求を並行して実行できる非同期モデルがあります。要求をキューに入れてFIFOを処理することは可能ですが、数百の要求がキューに入れられ、各要求の処理に100ミリ秒かかる場合、これはうまく拡張されません。

大量のトラフィックがある場合は、クエリを非同期に実行しない方がよい場合があります追加のリソースがないためリクエストを処理する。予備のリソースがない場合、リクエストは強制的にキューに入れられ、指数関数的に長くなるか完全に失敗します。その場合、非同期オーバーヘッド(ミューテックスとコンテキスト切り替え操作)は何も提供しません。

ASP.NETに関する限り、選択の余地はありません。非同期モデルを使用しているのは、サーバークライアントモデルにとって意味があるためです。すべてのリクエスト間で共有されているリソースを管理しようとしている場合を除き、非同期パターンを使用して独自に拡張パターンを使用するコードを内部で作成する場合、それらはすでにラップされているため、実際には改善は見られません他をブロックしない非同期プロセスで。

最終的には、システムのボトルネックの原因を実際に確認するまで、すべて主観的です。非同期パターンがどこで役立つかが明らかな場合があります(キューに入れられたリソースのブロックを防ぐことによって)。最終的には、システムの測定と分析のみが、どこで効率を上げることができるかを示すことができます。

編集:

あなたの例では、Task.Factory.StartNew呼び出しは.NETスレッドプールの操作をキューに入れます。スレッドプールスレッドの性質は再利用されます(多くのスレッドを作成/破壊するコストを回避するため)。操作が完了すると、スレッドはプールに戻され、別のリクエストで再利用されます(操作でオブジェクトを作成しない限り、ガベージコレクターは実際には関与しません。その場合、通常どおりに収集されますスコープ)。

ASP.NETに関する限り、ここには特別な操作はありません。 ASP.NET要求は、非同期タスクに関係なく完了します。唯一の懸念は、スレッドプールが飽和状態になっている場合(つまり、現在リクエストを処理できるスレッドが存在せず、プールの設定でそれ以上スレッドを作成できない場合)、リクエストがブロックされる場合ですタスクの開始を待機プールスレッドが使用可能になるまで。

6
Paul Turner

はい、スレッドプールのスレッドを使用します。実際には、MSDNの非常に優れたガイドがあり、すべての質問などに取り組んでいます。過去に非常に有用であることがわかりました。見てみな!

http://msdn.Microsoft.com/en-us/library/ee728598.aspx

その間、非同期コードについて聞いたコメントと提案は、一言で理解する必要があります。まず第一に、非同期の何かを作成するだけでは必ずしも拡張性が向上するわけではなく、場合によってはアプリケーションの拡張性が悪化する可能性もあります。 「膨大なトラフィック...」について投稿した他のコメントも、特定の状況でのみ正しいです。それは本当にあなたのオペレーションが何をしているか、そしてそれがシステムの他の部分とどのように相互作用するかに依存します。

要するに、多くの人々が非同期について多くの意見を持っていますが、彼らは文脈から外れているかもしれません。私はあなたの正確な問題に焦点を当て、基本的なパフォーマンステストを行って、非同期コントローラなどが実際にyourアプリケーションで処理するものを確認します。

2
A.R.

まず、MVCではなく、スレッドプールを管理するIISです。そのため、MVCまたはASP.NETアプリケーションへの要求は、スレッドプールで保持されているスレッドから提供されます。アプリを非同期にする場合にのみ、彼は別のスレッドでこのアクションを呼び出し、すぐにスレッドを解放して、他のリクエストを取得できるようにします。

MVCでスレッド不足がどのように発生するかを示す詳細ビデオ( http://www.youtube.com/watch?v=wvg13n5V0V0/ "MVC非同期コントローラーとスレッド不足")で同じことを説明しました。 MVC Asynchコントローラーを使用して最小化する方法.perfmonを使用して要求キューを測定したため、MVC非同期の要求キューがどのように減少し、Synch操作の最悪の状況を確認できます。

0