ASP.NET Core パフォーマンスのベストプラクティスASP.NET Core Performance Best Practices

作成者: Mike RousosBy Mike Rousos

この記事では、ASP.NET Core のパフォーマンスのベストプラクティスに関するガイドラインを示します。This article provides guidelines for performance best practices with ASP.NET Core.

積極的にキャッシュCache aggressively

キャッシュについては、このドキュメントのいくつかの部分で説明します。Caching is discussed in several parts of this document. 詳細については、「ASP.NET Core での応答のキャッシュ」を参照してください。For more information, see ASP.NET Core での応答のキャッシュ.

ホットコードパスについてUnderstand hot code paths

このドキュメントでは、 ホットコードパス は、頻繁に呼び出される、実行時間の大半が発生するコードパスとして定義されています。In this document, a hot code path is defined as a code path that is frequently called and where much of the execution time occurs. ホットコードパスは、通常、アプリのスケールアウトとパフォーマンスを制限し、このドキュメントのいくつかの部分で説明されています。Hot code paths typically limit app scale-out and performance and are discussed in several parts of this document.

ブロック呼び出しを回避するAvoid blocking calls

ASP.NET Core アプリは、多数の要求を同時に処理するように設計する必要があります。ASP.NET Core apps should be designed to process many requests simultaneously. 非同期 Api を使用すると、スレッドの小さなプールで、ブロックしている呼び出しを待機せずに、数千の同時要求を処理することができます。Asynchronous APIs allow a small pool of threads to handle thousands of concurrent requests by not waiting on blocking calls. 実行時間の長い同期タスクが完了するのを待機するのではなく、スレッドが別の要求で作業できます。Rather than waiting on a long-running synchronous task to complete, the thread can work on another request.

ASP.NET Core アプリのパフォーマンスに関する一般的な問題は、非同期の呼び出しをブロックしていることです。A common performance problem in ASP.NET Core apps is blocking calls that could be asynchronous. 多くの同期ブロッキング呼び出しは、 スレッドプールの枯渇 と応答時間の低下につながります。Many synchronous blocking calls lead to Thread Pool starvation and degraded response times.

実行 しない:Do not :

  • Task. Waitまたはtask. Resultを呼び出して、非同期実行をブロックします。Block asynchronous execution by calling Task.Wait or Task.Result.
  • 共通コードパスのロックを取得します。Acquire locks in common code paths. ASP.NET Core アプリは、コードを並列実行するように設計されている場合に最もパフォーマンスが高くなります。ASP.NET Core apps are most performant when architected to run code in parallel.
  • タスクを呼び出し ます。実行 してすぐに待機します。Call Task.Run and immediately await it. ASP.NET Core は、通常のスレッドプールのスレッドで既にアプリコードを実行しているため、タスクを呼び出すと、余分な不要なスレッドプールスケジュールが生成されます。ASP.NET Core already runs app code on normal Thread Pool threads, so calling Task.Run only results in extra unnecessary Thread Pool scheduling. スケジュールされたコードがスレッドをブロックする場合でも、タスクを実行しても、そのようなことはできません。Even if the scheduled code would block a thread, Task.Run does not prevent that.

操作 :Do :

  • ホットコードパスを非同期にします。Make hot code paths asynchronous.
  • 非同期 API が使用可能な場合は、データアクセス、i/o、長時間実行される操作 Api を非同期に呼び出します。Call data access, I/O, and long-running operations APIs asynchronously if an asynchronous API is available. 同期 API を非同期にするには、Run を使用しないでください Do not use Task.Run to make a synchronous API asynchronous.
  • コントローラー/ Razor ページのアクションを非同期にします。Make controller/Razor Page actions asynchronous. 非同期 /await パターンを活用するために、呼び出し履歴全体が非同期になります。The entire call stack is asynchronous in order to benefit from async/await patterns.

Perfviewなどのプロファイラーを使用して、スレッドプールに頻繁に追加されるスレッドを見つけることができます。A profiler, such as PerfView, can be used to find threads frequently added to the Thread Pool. イベントは、スレッド Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start プールに追加されたスレッドを示します。The Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start event indicates a thread added to the thread pool.

IEnumerable <T> または IAsyncEnumerable を返します<T>Return IEnumerable<T> or IAsyncEnumerable<T>

IEnumerable<T>アクションから戻ると、シリアライザーによって同期コレクションの反復処理が行われます。Returning IEnumerable<T> from an action results in synchronous collection iteration by the serializer. その結果、呼び出しがブロックされ、スレッド プールが枯渇する可能性があります。The result is the blocking of calls and a potential for thread pool starvation. 同期列挙を回避するには、を使用して列挙可能なを ToListAsync 返します。To avoid synchronous enumeration, use ToListAsync before returning the enumerable.

ASP.NET Core 3.0 以降では、を IAsyncEnumerable<T> 非同期的に列挙するの代替手段としてを使用でき IEnumerable<T> ます。Beginning with ASP.NET Core 3.0, IAsyncEnumerable<T> can be used as an alternative to IEnumerable<T> that enumerates asynchronously. 詳細については、「 コントローラーアクションの戻り値の型」を参照してください。For more information, see Controller action return types.

大きなオブジェクトの割り当てを最小限にするMinimize large object allocations

.Net Core ガベージコレクターは、ASP.NET Core アプリで自動的にメモリの割り当てと解放を管理します。The .NET Core garbage collector manages allocation and release of memory automatically in ASP.NET Core apps. 自動ガベージコレクションは、一般に、メモリが解放される方法とタイミングについて開発者が心配する必要がないことを意味します。Automatic garbage collection generally means that developers don't need to worry about how or when memory is freed. ただし、参照されていないオブジェクトをクリーンアップすると CPU 時間がかかるため、開発者は ホットコードパス内のオブジェクトの割り当てを最小限に抑える必要があります。However, cleaning up unreferenced objects takes CPU time, so developers should minimize allocating objects in hot code paths. ガベージコレクションは、大きなオブジェクト (> 85 K バイト) で特にコストが高くなります。Garbage collection is especially expensive on large objects (> 85 K bytes). ラージオブジェクトは 大きなオブジェクトヒープ に格納され、クリーンアップするには、完全な (ジェネレーション 2) ガベージコレクションが必要です。Large objects are stored on the large object heap and require a full (generation 2) garbage collection to clean up. ジェネレーション0およびジェネレーション1のコレクションとは異なり、ジェネレーション2のコレクションでは、アプリの実行を一時的に中断する必要があります。Unlike generation 0 and generation 1 collections, a generation 2 collection requires a temporary suspension of app execution. 大きなオブジェクトを頻繁に割り当てたり割り当て解除したりすると、パフォーマンスが低下する可能性があります。Frequent allocation and de-allocation of large objects can cause inconsistent performance.

推奨事項:Recommendations:

  • 頻繁に使用されるラージオブジェクトをキャッシュすること を検討してください。Do consider caching large objects that are frequently used. 大きなオブジェクトをキャッシュすると、コストのかかる割り当てを防ぐことができます。Caching large objects prevents expensive allocations.
  • 大きな配列を格納する には、 arraypool <T>を使用してバッファーをプールします。Do pool buffers by using an ArrayPool<T> to store large arrays.
  • ホットコードパスには、有効期間が短い大きなオブジェクトを多数割り当て ない でください。Do not allocate many, short-lived large objects on hot code paths.

前述のようなメモリの問題は、 Perfview でガベージコレクション (GC) の統計を確認して調べることによって診断できます。Memory issues, such as the preceding, can be diagnosed by reviewing garbage collection (GC) stats in PerfView and examining:

  • ガベージコレクションの一時停止時刻。Garbage collection pause time.
  • ガベージコレクションで消費されるプロセッサ時間の割合。What percentage of the processor time is spent in garbage collection.
  • ジェネレーション0、1、および2のガベージコレクションの数。How many garbage collections are generation 0, 1, and 2.

詳細については、「 ガベージコレクションとパフォーマンス」を参照してください。For more information, see Garbage Collection and Performance.

データアクセスと i/o を最適化するOptimize data access and I/O

多くの場合、データストアやその他のリモートサービスとのやり取りは、ASP.NET Core アプリの最も低速な部分です。Interactions with a data store and other remote services are often the slowest parts of an ASP.NET Core app. 効率的なパフォーマンスを利用するには、データの読み取りと書き込みを効率的に行うことが重要です。Reading and writing data efficiently is critical for good performance.

推奨事項:Recommendations:

  • すべてのデータアクセス Api を非同期 に呼び出しますDo call all data access APIs asynchronously.
  • 必要以上に多くのデータを取得 しないで ください。Do not retrieve more data than is necessary. 現在の HTTP 要求に必要なデータだけを返すクエリを記述します。Write queries to return just the data that's necessary for the current HTTP request.
  • 少し古いデータが許容される場合は、データベースまたはリモートサービスから取得した頻繁にアクセスされるデータをキャッシュすること を検討してください。Do consider caching frequently accessed data retrieved from a database or remote service if slightly out-of-date data is acceptable. シナリオに応じて、 Memorycache または microsoft.web.distributedcacheを使用します。Depending on the scenario, use a MemoryCache or a DistributedCache. 詳細については、「ASP.NET Core での応答のキャッシュ」を参照してください。For more information, see ASP.NET Core での応答のキャッシュ.
  • ネットワークラウンドトリップ を最小限に 抑えます。Do minimize network round trips. 目的は、複数の呼び出しではなく、1回の呼び出しで必要なデータを取得することです。The goal is to retrieve the required data in a single call rather than several calls.
  • 読み取り専用の目的でデータにアクセスする場合 、Entity Framework Core で 追跡なしのクエリを使用します。Do use no-tracking queries in Entity Framework Core when accessing data for read-only purposes. EF Core は、追跡しないクエリの結果をより効率的に返すことができます。EF Core can return the results of no-tracking queries more efficiently.
  • (、、またはステートメントを使用して) LINQ クエリのフィルター処理と集計を 実行 し、 .Where .Select .Sum データベースによってフィルター処理が実行されるようにします。Do filter and aggregate LINQ queries (with .Where, .Select, or .Sum statements, for example) so that the filtering is performed by the database.
  • EF Core に よってクライアント 上の一部のクエリ演算子が解決されるため、クエリの実行効率が悪くなることがあります。Do consider that EF Core resolves some query operators on the client, which may lead to inefficient query execution. 詳細については、「 クライアント評価のパフォーマンスの問題」を参照してください。For more information, see Client evaluation performance issues.
  • コレクションに対してプロジェクションクエリを使用しないでください。これにより、"N + 1" 個の SQL クエリが実行される可能性が あり ます。Do not use projection queries on collections, which can result in executing "N + 1" SQL queries. 詳細については、「 相関サブクエリの最適化」を参照してください。For more information, see Optimization of correlated subqueries.

高スケールアプリのパフォーマンスを向上させる方法については、「 EF High performance 」を参照してください。See EF High Performance for approaches that may improve performance in high-scale apps:

コードベースをコミットする前に、上記の高パフォーマンスアプローチの影響を測定することをお勧めします。We recommend measuring the impact of the preceding high-performance approaches before committing the code base. コンパイル済みクエリの複雑さが増えると、パフォーマンスの向上には合わない可能性があります。The additional complexity of compiled queries may not justify the performance improvement.

Application Insightsまたはプロファイリングツールでデータにアクセスするために費やされた時間を確認することで、クエリの問題を検出できます。Query issues can be detected by reviewing the time spent accessing data with Application Insights or with profiling tools. ほとんどのデータベースでは、頻繁に実行されるクエリに関する統計も利用できます。Most databases also make statistics available concerning frequently executed queries.

HttpClientFactory を使用して HTTP 接続をプールするPool HTTP connections with HttpClientFactory

Httpclientはインターフェイスを実装してい IDisposable ますが、再利用できるように設計されています。Although HttpClient implements the IDisposable interface, it's designed for reuse. 閉じら HttpClient れたインスタンスは、短時間にわたって状態でソケットを開いたままに TIME_WAIT します。Closed HttpClient instances leave sockets open in the TIME_WAIT state for a short period of time. オブジェクトを作成および破棄するコードパス HttpClient が頻繁に使用される場合、アプリは使用可能なソケットを消費する可能性があります。If a code path that creates and disposes of HttpClient objects is frequently used, the app may exhaust available sockets. HttpClientFactory は、この問題の解決策として ASP.NET Core 2.1 で導入されました。HttpClientFactory was introduced in ASP.NET Core 2.1 as a solution to this problem. これは、パフォーマンスと信頼性を最適化するために、プール HTTP 接続を処理します。It handles pooling HTTP connections to optimize performance and reliability.

推奨事項:Recommendations:

共通コードパスを高速に保つKeep common code paths fast

すべてのコードが高速になるようにします。You want all of your code to be fast. 頻繁に呼び出されるコードパスは、最適化するうえで最も重要なものです。Frequently-called code paths are the most critical to optimize. 次の設定があります。These include:

  • アプリケーションの要求処理パイプラインのミドルウェアコンポーネント (特に、ミドルウェアはパイプラインの早い段階で実行されます)。Middleware components in the app's request processing pipeline, especially middleware run early in the pipeline. これらのコンポーネントは、パフォーマンスに大きな影響を与えます。These components have a large impact on performance.
  • 要求ごとに、または要求ごとに複数回実行されるコード。Code that's executed for every request or multiple times per request. たとえば、カスタムログ、承認ハンドラー、または一時的なサービスの初期化などです。For example, custom logging, authorization handlers, or initialization of transient services.

推奨事項:Recommendations:

HTTP 要求の外部で実行時間の長いタスクを完了するComplete long-running Tasks outside of HTTP requests

ASP.NET Core アプリに対するほとんどの要求は、必要なサービスを呼び出すコントローラーまたはページモデルによって処理され、HTTP 応答を返すことができます。Most requests to an ASP.NET Core app can be handled by a controller or page model calling necessary services and returning an HTTP response. 長時間実行されるタスクが含まれている一部の要求では、要求-応答プロセス全体を非同期にすることをお勧めします。For some requests that involve long-running tasks, it's better to make the entire request-response process asynchronous.

推奨事項:Recommendations:

  • 通常の HTTP 要求処理の一部として、長時間実行されるタスクが完了するまで待機しない ください。Do not wait for long-running tasks to complete as part of ordinary HTTP request processing.
  • バックグラウンドサービスで長時間実行される要求や、 Azure 関数を使用したアウトプロセスを処理すること を検討してください。Do consider handling long-running requests with background services or out of process with an Azure Function. 特に、CPU を集中的に使用するタスクには、アウトプロセスの完了が役立ちます。Completing work out-of-process is especially beneficial for CPU-intensive tasks.
  • クライアントとの非同期通信には、などのリアルタイム通信オプション を使用し SignalR ます。Do use real-time communication options, such as SignalR, to communicate with clients asynchronously.

クライアント資産の縮小Minify client assets

複雑なフロントエンドを使用する ASP.NET Core アプリは、多くの JavaScript、CSS、またはイメージファイルに頻繁に対応します。ASP.NET Core apps with complex front-ends frequently serve many JavaScript, CSS, or image files. 初期読み込み要求のパフォーマンスは、次の方法で改善できます。Performance of initial load requests can be improved by:

  • バンドル。複数のファイルを1つに結合します。Bundling, which combines multiple files into one.
  • 縮小。空白とコメントを削除することによってファイルのサイズを縮小します。Minifying, which reduces the size of files by removing whitespace and comments.

推奨事項:Recommendations:

  • クライアント資産のバンドルと縮小には ASP.NET Core の 組み込みサポートを使用し ます。Do use ASP.NET Core's built-in support for bundling and minifying client assets.
  • 複雑なクライアント資産管理には、 Webpackなどの他のサードパーティ製ツールを使用すること を検討してください。Do consider other third-party tools, such as Webpack, for complex client asset management.

応答を圧縮するCompress responses

通常、応答のサイズを小さくすると、アプリの応答性が大幅に向上します。Reducing the size of the response usually increases the responsiveness of an app, often dramatically. ペイロードサイズを減らす方法の1つは、アプリの応答を圧縮することです。One way to reduce payload sizes is to compress an app's responses. 詳細については、「 応答の圧縮」を参照してください。For more information, see Response compression.

最新の ASP.NET Core リリースを使用するUse the latest ASP.NET Core release

ASP.NET Core の新しいリリースにはそれぞれ、パフォーマンスが向上しています。Each new release of ASP.NET Core includes performance improvements. .NET Core と ASP.NET Core での最適化により、新しいバージョンの方が以前のバージョンより高い値になります。Optimizations in .NET Core and ASP.NET Core mean that newer versions generally outperform older versions. たとえば、.NET Core 2.1 では、コンパイルされた正規表現と享受をスパン <T> からサポートするようになりました。For example, .NET Core 2.1 added support for compiled regular expressions and benefitted from Span<T>. ASP.NET Core 2.2 では、HTTP/2 のサポートが追加されました。ASP.NET Core 2.2 added support for HTTP/2. ASP.NET Core 3.0 では、メモリ使用量を減らし、スループットを向上させる多くの機能強化が加えられています。ASP.NET Core 3.0 adds many improvements that reduce memory usage and improve throughput. パフォーマンスが優先される場合は、現在のバージョンの ASP.NET Core にアップグレードすることを検討してください。If performance is a priority, consider upgrading to the current version of ASP.NET Core.

例外を最小化するMinimize exceptions

例外はめったに発生しません。Exceptions should be rare. 例外のスローとキャッチは、他のコードフローパターンと比べて低速です。Throwing and catching exceptions is slow relative to other code flow patterns. このため、通常のプログラムフローを制御するために例外を使用することはできません。Because of this, exceptions shouldn't be used to control normal program flow.

推奨事項:Recommendations:

  • 特に ホットコードパスでは、通常のプログラムフローの手段として例外をスローまたはキャッチし ない ようにします。Do not use throwing or catching exceptions as a means of normal program flow, especially in hot code paths.
  • 例外を発生させる条件を検出して処理するには、アプリにロジック を含めますDo include logic in the app to detect and handle conditions that would cause an exception.
  • 例外的または予期しない条件の例外 をスローまた はキャッチします。Do throw or catch exceptions for unusual or unexpected conditions.

Application Insights などのアプリ診断ツールを使用すると、アプリケーションでのパフォーマンスに影響する可能性のある一般的な例外を識別できます。App diagnostic tools, such as Application Insights, can help to identify common exceptions in an app that may affect performance.

パフォーマンスと信頼性Performance and reliability

次のセクションでは、パフォーマンスのヒントと既知の信頼性の問題と解決策について説明します。The following sections provide performance tips and known reliability problems and solutions.

HttpRequest/Httpresponse.cache 本体で同期読み取りまたは書き込みを回避するAvoid synchronous read or write on HttpRequest/HttpResponse body

ASP.NET Core の i/o はすべて非同期です。All I/O in ASP.NET Core is asynchronous. サーバーは、 Stream 同期と非同期の両方のオーバーロードを持つインターフェイスを実装します。Servers implement the Stream interface, which has both synchronous and asynchronous overloads. スレッドプールのスレッドがブロックされないようにするために、非同期のものを使用することをお勧めします。The asynchronous ones should be preferred to avoid blocking thread pool threads. ブロックしているスレッドは、スレッドプールの枯渇につながる可能性があります。Blocking threads can lead to thread pool starvation.

この 操作は避けてください。次の例では、を使用し ReadToEnd ます。Do not do this: The following example uses the ReadToEnd. このメソッドは、現在のスレッドが結果を待機するのをブロックします。It blocks the current thread to wait for the result. 非同期的な 同期の例を次に示します。This is an example of sync over async.

public class BadStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public ActionResult<ContosoData> Get()
    {
        var json = new StreamReader(Request.Body).ReadToEnd();

        return JsonSerializer.Deserialize<ContosoData>(json);
    }
}

上記のコードでは、は、 Get HTTP 要求の本体全体をメモリに同期的に読み取ります。In the preceding code, Get synchronously reads the entire HTTP request body into memory. クライアントが低速でアップロードしている場合、アプリは非同期で同期を実行しています。If the client is slowly uploading, the app is doing sync over async. Kestrel では同期読み取りがサポート されてい ないため、アプリは非同期経由で同期します。The app does sync over async because Kestrel does NOT support synchronous reads.

次の 手順を実行します。 次の例では、を使用して、 ReadToEndAsync 読み取り中にスレッドをブロックしません。Do this: The following example uses ReadToEndAsync and does not block the thread while reading.

public class GoodStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public async Task<ActionResult<ContosoData>> Get()
    {
        var json = await new StreamReader(Request.Body).ReadToEndAsync();

        return JsonSerializer.Deserialize<ContosoData>(json);
    }

}

上記のコードは、HTTP 要求本文全体を非同期的にメモリに読み込みます。The preceding code asynchronously reads the entire HTTP request body into memory.

警告

要求が大きい場合、HTTP 要求本文全体をメモリに読み込むと、メモリ不足 (OOM) 状態になる可能性があります。If the request is large, reading the entire HTTP request body into memory could lead to an out of memory (OOM) condition. OOM を使用すると、サービス拒否が発生する可能性があります。OOM can result in a Denial Of Service. 詳細については、このドキュメントの「 大きな要求本文または応答本文をメモリに読み込む ことを避ける」を参照してください。For more information, see Avoid reading large request bodies or response bodies into memory in this document.

次の 手順を実行します。 次の例は、バッファーされていない要求本文を使用する完全な非同期です。Do this: The following example is fully asynchronous using a non buffered request body:

public class GoodStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public async Task<ActionResult<ContosoData>> Get()
    {
        return await JsonSerializer.DeserializeAsync<ContosoData>(Request.Body);
    }
}

上記のコードは、要求本文を非同期的にシリアル化解除して C# オブジェクトにします。The preceding code asynchronously de-serializes the request body into a C# object.

要求で ReadFormAsync を優先します。フォームPrefer ReadFormAsync over Request.Form

HttpContext.Request.Form ではなく HttpContext.Request.ReadFormAsync を使用します。Use HttpContext.Request.ReadFormAsync instead of HttpContext.Request.Form. HttpContext.Request.Form は、次の条件で安全に読み取ることができます。HttpContext.Request.Form can be safely read only with the following conditions:

  • フォームは、の呼び出しによって読み取られました。 ReadFormAsyncThe form has been read by a call to ReadFormAsync, and
  • キャッシュされたフォーム値はを使用して読み取られています HttpContext.Request.FormThe cached form value is being read using HttpContext.Request.Form

この 操作は避けてください。次の例では、 HttpContext.Request.Form を使用します。Do not do this: The following example uses HttpContext.Request.Form. HttpContext.Request.Form非同期の同期を使用し、スレッドプールの枯渇につながる可能性があります。HttpContext.Request.Form uses sync over async and can lead to thread pool starvation.

public class BadReadController : Controller
{
    [HttpPost("/form-body")]
    public IActionResult Post()
    {
        var form =  HttpContext.Request.Form;

        Process(form["id"], form["name"]);

        return Accepted();
    }

次の 手順を実行します。 次の例では、を使用して HttpContext.Request.ReadFormAsync フォーム本文を非同期に読み取ります。Do this: The following example uses HttpContext.Request.ReadFormAsync to read the form body asynchronously.

public class GoodReadController : Controller
{
    [HttpPost("/form-body")]
    public async Task<IActionResult> Post()
    {
       var form = await HttpContext.Request.ReadFormAsync();

        Process(form["id"], form["name"]);

        return Accepted();
    }

大きな要求本文または応答本文をメモリに読み込むことは避けてくださいAvoid reading large request bodies or response bodies into memory

.NET では、85 KB を超えるすべてのオブジェクト割り当ては、大きなオブジェクトヒープ (LOH) で終了します。In .NET, every object allocation greater than 85 KB ends up in the large object heap (LOH). ラージオブジェクトは、次の2つの方法でコストが高くなります。Large objects are expensive in two ways:

  • 新しく割り当てられたラージオブジェクトのメモリをクリアする必要があるため、割り当てコストが高くなります。The allocation cost is high because the memory for a newly allocated large object has to be cleared. CLR は、新しく割り当てられたすべてのオブジェクトのメモリがクリアされることを保証します。The CLR guarantees that memory for all newly allocated objects is cleared.
  • LOH は、ヒープの残りの部分で収集されます。LOH is collected with the rest of the heap. LOH には、フル ガベージコレクション または Gen2 collectionが必要です。LOH requires a full garbage collection or Gen2 collection.

この ブログ投稿 では、問題について簡潔に説明しています。This blog post describes the problem succinctly:

ラージオブジェクトが割り当てられると、ジェネレーション2のオブジェクトとしてマークされます。When a large object is allocated, it's marked as Gen 2 object. 小さいオブジェクトの場合は Gen 0 として生成されません。Not Gen 0 as for small objects. 結果として、LOH でメモリが不足していると、LOH だけでなく、マネージヒープ全体が GC によってクリーンアップされます。The consequences are that if you run out of memory in LOH, GC cleans up the whole managed heap, not only LOH. そのため、LOH を含む Gen 0、Gen 1、Gen 2 をクリーンアップします。So it cleans up Gen 0, Gen 1 and Gen 2 including LOH. これはフルガベージコレクションと呼ばれ、最も時間のかかるガベージコレクションです。This is called full garbage collection and is the most time-consuming garbage collection. 多くのアプリケーションでは、許容される可能性があります。For many applications, it can be acceptable. しかし、高パフォーマンスの web サーバーでは、平均的な web 要求を処理するために大量のメモリバッファーが必要になることはありません (ソケットからの読み取り、圧縮解除、JSON & のデコードなど)。But definitely not for high-performance web servers, where few big memory buffers are needed to handle an average web request (read from a socket, decompress, decode JSON & more).

大規模な要求または応答の本文を1つのまたはに格納するネイティブ byte[] string :Naively storing a large request or response body into a single byte[] or string:

  • LOH の領域がすぐに不足する可能性があります。May result in quickly running out of space in the LOH.
  • 完全な Gc が実行されているため、アプリのパフォーマンスの問題が発生する可能性があります。May cause performance issues for the app because of full GCs running.

同期データ処理 API を使用した作業Working with a synchronous data processing API

同期読み取りと書き込みのみをサポートするシリアライザー/デシリアライザーを使用する場合 (たとえば、 JSON.NET):When using a serializer/de-serializer that only supports synchronous reads and writes (for example, JSON.NET):

  • シリアライザー/デシリアライザーに渡す前に、データをメモリに非同期的にバッファーします。Buffer the data into memory asynchronously before passing it into the serializer/de-serializer.

警告

要求が大きい場合、メモリ不足 (OOM) 状態になる可能性があります。If the request is large, it could lead to an out of memory (OOM) condition. OOM を使用すると、サービス拒否が発生する可能性があります。OOM can result in a Denial Of Service. 詳細については、このドキュメントの「 大きな要求本文または応答本文をメモリに読み込む ことを避ける」を参照してください。For more information, see Avoid reading large request bodies or response bodies into memory in this document.

ASP.NET Core 3.0 は System.Text.Json 、JSON のシリアル化に既定でを使用します。ASP.NET Core 3.0 uses System.Text.Json by default for JSON serialization. System.Text.Json:System.Text.Json:

  • 非同期で JSON の読み取りと書き込みを行います。Reads and writes JSON asynchronously.
  • UTF-8 テキスト用に最適化されています。Is optimized for UTF-8 text.
  • 通常、Newtonsoft.Json よりパフォーマンスが向上します。Typically higher performance than Newtonsoft.Json.

フィールドに IHttpContextAccessor を格納しないDo not store IHttpContextAccessor.HttpContext in a field

IHttpContextAccessorは、 HttpContext 要求スレッドからアクセスされたときに、アクティブな要求のを返します。The IHttpContextAccessor.HttpContext returns the HttpContext of the active request when accessed from the request thread. IHttpContextAccessor.HttpContext フィールドまたは変数に格納することはでき ませんThe IHttpContextAccessor.HttpContext should not be stored in a field or variable.

この 操作は避けてください。次の例では、を HttpContext フィールドに格納し、後でそれを使用しようとします。Do not do this: The following example stores the HttpContext in a field and then attempts to use it later.

public class MyBadType
{
    private readonly HttpContext _context;
    public MyBadType(IHttpContextAccessor accessor)
    {
        _context = accessor.HttpContext;
    }

    public void CheckAdmin()
    {
        if (!_context.User.IsInRole("admin"))
        {
            throw new UnauthorizedAccessException("The current user isn't an admin");
        }
    }
}

上記のコードでは、コンストラクター内で null または正しくないが頻繁にキャプチャされ HttpContext ます。The preceding code frequently captures a null or incorrect HttpContext in the constructor.

次の 手順を実行します。 次に例を示します。Do this: The following example:

  • IHttpContextAccessor フィールドに格納します。Stores the IHttpContextAccessor in a field.
  • は、 HttpContext 正しい時刻にフィールドを使用し、をチェックし null ます。Uses the HttpContext field at the correct time and checks for null.
public class MyGoodType
{
    private readonly IHttpContextAccessor _accessor;
    public MyGoodType(IHttpContextAccessor accessor)
    {
        _accessor = accessor;
    }

    public void CheckAdmin()
    {
        var context = _accessor.HttpContext;
        if (context != null && !context.User.IsInRole("admin"))
        {
            throw new UnauthorizedAccessException("The current user isn't an admin");
        }
    }
}

複数のスレッドから HttpContext にアクセスしないDo not access HttpContext from multiple threads

HttpContext はスレッドセーフでは ありませんHttpContext is NOT thread-safe. HttpContext複数のスレッドから同時にアクセスすると、ハング、クラッシュ、データ破損などの未定義の動作が発生する可能性があります。Accessing HttpContext from multiple threads in parallel can result in undefined behavior such as hangs, crashes, and data corruption.

この 操作は避けてください。次の例では、3つの並列要求を行い、発信 HTTP 要求の前後に受信要求パスをログに記録します。Do not do this: The following example makes three parallel requests and logs the incoming request path before and after the outgoing HTTP request. 要求パスは、複数のスレッドから同時にアクセスされる可能性があります。The request path is accessed from multiple threads, potentially in parallel.

public class AsyncBadSearchController : Controller
{       
    [HttpGet("/search")]
    public async Task<SearchResults> Get(string query)
    {
        var query1 = SearchAsync(SearchEngine.Google, query);
        var query2 = SearchAsync(SearchEngine.Bing, query);
        var query3 = SearchAsync(SearchEngine.DuckDuckGo, query);

        await Task.WhenAll(query1, query2, query3);

        var results1 = await query1;
        var results2 = await query2;
        var results3 = await query3;

        return SearchResults.Combine(results1, results2, results3);
    }       

    private async Task<SearchResults> SearchAsync(SearchEngine engine, string query)
    {
        var searchResults = _searchService.Empty();
        try
        {
            _logger.LogInformation("Starting search query from {path}.", 
                                    HttpContext.Request.Path);
            searchResults = _searchService.Search(engine, query);
            _logger.LogInformation("Finishing search query from {path}.", 
                                    HttpContext.Request.Path);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed query from {path}", 
                             HttpContext.Request.Path);
        }

        return await searchResults;
    }

次の 手順を実行します。 次の例では、3つの並列要求を行う前に、受信要求からすべてのデータをコピーします。Do this: The following example copies all data from the incoming request before making the three parallel requests.

public class AsyncGoodSearchController : Controller
{       
    [HttpGet("/search")]
    public async Task<SearchResults> Get(string query)
    {
        string path = HttpContext.Request.Path;
        var query1 = SearchAsync(SearchEngine.Google, query,
                                 path);
        var query2 = SearchAsync(SearchEngine.Bing, query, path);
        var query3 = SearchAsync(SearchEngine.DuckDuckGo, query, path);

        await Task.WhenAll(query1, query2, query3);

        var results1 = await query1;
        var results2 = await query2;
        var results3 = await query3;

        return SearchResults.Combine(results1, results2, results3);
    }

    private async Task<SearchResults> SearchAsync(SearchEngine engine, string query,
                                                  string path)
    {
        var searchResults = _searchService.Empty();
        try
        {
            _logger.LogInformation("Starting search query from {path}.",
                                   path);
            searchResults = await _searchService.SearchAsync(engine, query);
            _logger.LogInformation("Finishing search query from {path}.", path);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed query from {path}", path);
        }

        return await searchResults;
    }

要求の完了後に HttpContext を使用しないDo not use the HttpContext after the request is complete

HttpContext は、ASP.NET Core パイプラインにアクティブな HTTP 要求がある限り有効です。HttpContext is only valid as long as there is an active HTTP request in the ASP.NET Core pipeline. ASP.NET Core パイプライン全体は、すべての要求を実行するデリゲートの非同期チェーンです。The entire ASP.NET Core pipeline is an asynchronous chain of delegates that executes every request. Taskこのチェーンから返されたが完了すると、 HttpContext がリサイクルされます。When the Task returned from this chain completes, the HttpContext is recycled.

この 操作は避けてください。次の例では、 async void 最初のに到達したときに HTTP 要求が完了するように、を使用して await います。Do not do this: The following example uses async void which makes the HTTP request complete when the first await is reached:

  • これは、ASP.NET Core アプリでは に不適切な方法です。Which is ALWAYS a bad practice in ASP.NET Core apps.
  • HttpResponseHTTP 要求が完了した後に、にアクセスします。Accesses the HttpResponse after the HTTP request is complete.
  • プロセスをクラッシュさせる。Crashes the process.
public class AsyncBadVoidController : Controller
{
    [HttpGet("/async")]
    public async void Get()
    {
        await Task.Delay(1000);

        // The following line will crash the process because of writing after the 
        // response has completed on a background thread. Notice async void Get()

        await Response.WriteAsync("Hello World");
    }
}

次の 手順を実行します。 次の例では、を Task フレームワークに返します。そのため、アクションが完了するまで HTTP 要求は完了しません。Do this: The following example returns a Task to the framework, so the HTTP request doesn't complete until the action completes.

public class AsyncGoodTaskController : Controller
{
    [HttpGet("/async")]
    public async Task Get()
    {
        await Task.Delay(1000);

        await Response.WriteAsync("Hello World");
    }
}

バックグラウンドスレッドで HttpContext をキャプチャしないDo not capture the HttpContext in background threads

この 操作は避けてください。次の例は、プロパティからをキャプチャしていることを示してい HttpContext Controller ます。Do not do this: The following example shows a closure is capturing the HttpContext from the Controller property. 作業項目は次のようになる可能性があるため、この方法は不適切です。This is a bad practice because the work item could:

  • 要求スコープの外部で実行します。Run outside of the request scope.
  • 間違ったを読み取ろうとしました HttpContextAttempt to read the wrong HttpContext.
[HttpGet("/fire-and-forget-1")]
public IActionResult BadFireAndForget()
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        var path = HttpContext.Request.Path;
        Log(path);
    });

    return Accepted();
}

次の 手順を実行します。 次に例を示します。Do this: The following example:

  • 要求中にバックグラウンドタスクで必要なデータをコピーします。Copies the data required in the background task during the request.
  • コントローラーから何も参照していません。Doesn't reference anything from the controller.
[HttpGet("/fire-and-forget-3")]
public IActionResult GoodFireAndForget()
{
    string path = HttpContext.Request.Path;
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        Log(path);
    });

    return Accepted();
}

バックグラウンドタスクはホステッドサービスとして実装する必要があります。Background tasks should be implemented as hosted services. 詳細については、「Background tasks with hosted services」(ホストされるタスクを使用するバックグラウンド タスク) を参照してください。For more information, see Background tasks with hosted services.

バックグラウンドスレッドでコントローラーに挿入されたサービスをキャプチャしないDo not capture services injected into the controllers on background threads

この 操作は避けてください。次の例は、アクションパラメーターからをキャプチャすることを示してい DbContext Controller ます。Do not do this: The following example shows a closure is capturing the DbContext from the Controller action parameter. これは不適切な方法です。This is a bad practice. この作業項目は、要求スコープの外部で実行される可能性があります。The work item could run outside of the request scope. ContosoDbContext 要求に対してスコープが設定され、結果としてが生成され ObjectDisposedException ます。The ContosoDbContext is scoped to the request, resulting in an ObjectDisposedException.

[HttpGet("/fire-and-forget-1")]
public IActionResult FireAndForget1([FromServices]ContosoDbContext context)
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        context.Contoso.Add(new Contoso());
        await context.SaveChangesAsync();
    });

    return Accepted();
}

次の 手順を実行します。 次に例を示します。Do this: The following example:

  • IServiceScopeFactoryバックグラウンド作業項目のスコープを作成するために、を挿入します。Injects an IServiceScopeFactory in order to create a scope in the background work item. IServiceScopeFactory はシングルトンです。IServiceScopeFactory is a singleton.
  • バックグラウンドスレッドで新しい依存関係挿入スコープを作成します。Creates a new dependency injection scope in the background thread.
  • コントローラーから何も参照していません。Doesn't reference anything from the controller.
  • は、受信要求からをキャプチャしません ContosoDbContextDoesn't capture the ContosoDbContext from the incoming request.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory 
                                    serviceScopeFactory)
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        using (var scope = serviceScopeFactory.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();

            context.Contoso.Add(new Contoso());

            await context.SaveChangesAsync();                                        
        }
    });

    return Accepted();
}

次の強調表示されたコード。The following highlighted code:

  • バックグラウンド操作の有効期間のスコープを作成し、そこからサービスを解決します。Creates a scope for the lifetime of the background operation and resolves services from it.
  • ContosoDbContext 、正しいスコープからを使用します。Uses ContosoDbContext from the correct scope.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory 
                                    serviceScopeFactory)
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        using (var scope = serviceScopeFactory.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();

            context.Contoso.Add(new Contoso());

            await context.SaveChangesAsync();                                        
        }
    });

    return Accepted();
}

応答本文が開始された後に状態コードまたはヘッダーを変更しないDo not modify the status code or headers after the response body has started

ASP.NET Core は、HTTP 応答の本文をバッファーしません。ASP.NET Core does not buffer the HTTP response body. 最初に応答が書き込まれたとき:The first time the response is written:

  • ヘッダーは、本文のそのチャンクと共にクライアントに送信されます。The headers are sent along with that chunk of the body to the client.
  • 応答ヘッダーを変更することはできなくなりました。It's no longer possible to change response headers.

この 操作は避けてください。次のコードでは、応答が既に開始された後に応答ヘッダーを追加しようとしています。Do not do this: The following code tries to add response headers after the response has already started:

app.Use(async (context, next) =>
{
    await next();

    context.Response.Headers["test"] = "test value";
});

前のコードでは、 context.Response.Headers["test"] = "test value"; が応答に書き込まれた場合、は例外をスロー next() します。In the preceding code, context.Response.Headers["test"] = "test value"; will throw an exception if next() has written to the response.

次の 手順を実行します。 次の例では、ヘッダーを変更する前に、HTTP 応答が開始されたかどうかを確認します。Do this: The following example checks if the HTTP response has started before modifying the headers.

app.Use(async (context, next) =>
{
    await next();

    if (!context.Response.HasStarted)
    {
        context.Response.Headers["test"] = "test value";
    }
});

次の 手順を実行します。 次の例では、を使用して、 HttpResponse.OnStarting 応答ヘッダーをクライアントにフラッシュする前にヘッダーを設定します。Do this: The following example uses HttpResponse.OnStarting to set the headers before the response headers are flushed to the client.

応答が開始されていないかどうかを確認すると、応答ヘッダーが書き込まれる直前に呼び出されるコールバックを登録できます。Checking if the response has not started allows registering a callback that will be invoked just before response headers are written. 応答が開始されていないかどうかを確認しています:Checking if the response has not started:

  • ヘッダーをジャストインタイムで追加またはオーバーライドする機能を提供します。Provides the ability to append or override headers just in time.
  • では、パイプライン内の次のミドルウェアに関する知識は必要ありません。Doesn't require knowledge of the next middleware in the pipeline.
app.Use(async (context, next) =>
{
    context.Response.OnStarting(() =>
    {
        context.Response.Headers["someheader"] = "somevalue";
        return Task.CompletedTask;
    });

    await next();
});

応答本文への書き込みを既に開始している場合は、next () を呼び出さないでください。Do not call next() if you have already started writing to the response body

コンポーネントは、応答を処理および操作できる場合にのみ呼び出されることを想定しています。Components only expect to be called if it's possible for them to handle and manipulate the response.

IIS でインプロセスホスティングを使用するUse In-process hosting with IIS

インプロセス ホスティング モデルを使用する場合、ASP.NET Core アプリはその IIS ワーカー プロセスと同じプロセスで実行されます。Using in-process hosting, an ASP.NET Core app runs in the same process as its IIS worker process. インプロセスホスティングでは、要求がループバックアダプターを介してプロキシされないため、アウトプロセスホスティングよりパフォーマンスが向上します。In-process hosting provides improved performance over out-of-process hosting because requests aren't proxied over the loopback adapter. ループバックアダプターは、同じコンピューターに送信されるネットワークトラフィックを返すネットワークインターフェイスです。The loopback adapter is a network interface that returns outgoing network traffic back to the same machine. IIS では Windows プロセス アクティブ化サービス (WAS) を使用してプロセス管理が処理されます。IIS handles process management with the Windows Process Activation Service (WAS).

プロジェクトは、既定では ASP.NET Core 3.0 以降のインプロセスホスティングモデルに設定されます。Projects default to the in-process hosting model in ASP.NET Core 3.0 and later.

詳細については、「 IIS を使用した Windows での ASP.NET Core のホスト」を参照してください。For more information, see Host ASP.NET Core on Windows with IIS