Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the method of ASP.NET Core performance optimization

2025-02-25 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/01 Report--

This article focuses on "what is the method of ASP.NET Core performance optimization", interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn "what is the method of ASP.NET Core performance optimization"?

Understand the hot paths in the code

In this document, the code hotspot path is defined as the frequently called code path and most of the execution time. Code hotspot paths typically limit the extension and performance of applications and are discussed in several sections of this document.

Avoid blocking calls

ASP.NET Core applications should be designed to handle many requests simultaneously. Asynchronous API can use a small pool thread to handle thousands of concurrent requests through non-blocking calls. The thread can process another request instead of waiting for the long-running synchronization task to complete.

Common performance problems in ASP.NET Core applications are usually caused by calls that can be called asynchronously but are blocked. Synchronous blocking results in thread pool starvation and response time degradation.

Do not:

Block asynchronous execution by calling Task.Wait or Task.Result.

Lock in the common code path. ASP.NET Core applications should be designed to run code in parallel to maximize performance.

Call Task.Run and immediately await. ASP.NET Core itself is already running application code on thread pool threads, so calling Task.Run in this way will only result in additional unnecessary thread pool scheduling. And even if the scheduled code blocks threads, Task.Run doesn't avoid this situation, which makes no sense.

To:

Make sure that the code hotspot paths are all asynchronized.

For example, there is an asynchronous API available when API is used for calling data reads and writes, I _ Candle O processing, and long-term operations. Then be sure to choose asynchronous API. However, do not use Task.Run to wrap synchronous API to make it asynchronized.

Ensure that controller/Razor Page actions is asynchronized. The entire call stack is asynchronous, so you can take advantage of the performance advantages of the async/await pattern.

Use a performance analyzer, such as PerfView, to find threads that are frequently added to the thread pool. The Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start event indicates that a new thread was added to the thread pool.

Use IEumerable or IAsyncEnumerable as the return value

Returning IEumerable in Action will be iterated synchronously in the serializer. The result can be blocking or thread pool hunger. To avoid synchronizing iterative collections, you can use ToListAsync to asynchronize them before returning them.

Starting with ASP.NET Core 3.0, IAsyncEnumerable can be used as an alternative to IEumerable to iterate asynchronously. For more information, see return value types for Controller Action.

Use as few large objects as possible

The .NET Core garbage collector automatically manages the allocation and release of memory in ASP.NET Core applications. Automatic garbage collection usually means that developers don't have to worry about how or when to free memory. However, cleaning up unreferenced objects will take up CPU time, so developers should minimize allocated objects in the code hotspot path. Garbage collection is expensive on large objects (> 85 K bytes). Large objects are stored on large object heap and need full (generation 2) garbage collection to clean up. Unlike generation 0 and generation 1, generation 2 needs to temporarily suspend the application. Therefore, frequent allocation and unallocation of large objects can lead to performance attrition.

Recommendations:

Consider large objects that are frequently used by caches. Caching large objects prevents expensive allocation overhead.

Use ArrayPool as a pooled buffer to hold large arrays.

Do not allocate many large objects with a short life cycle on the code hotspot path.

You can diagnose and check for memory problems by viewing garbage collection (GC) statistics in PerfView, including:

Garbage collection hang time.

The percentage of processor time consumed in garbage collection.

How many garbage collections occur in generation 0, 1, and 2.

For more information, see garbage Collection and performance.

Optimize data manipulation and Istroke O

Interaction with data stores and other remote services is usually the slowest part of an ASP.NET Core application. Efficient reading and writing of data is essential for good performance.

Recommendations:

To invoke all data access API asynchronously.

Do not read unwanted data. When writing a query, only the data necessary for the current HTTP request is returned.

Consider caching frequently accessed data retrieved from a database or remote service (if slightly obsolete data is acceptable). Depending on the scenario, you can use MemoryCache or DistributedCache. For more information, see https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-3.1.

Try to minimize network round trips. If it can be done in a single call, it should not be called multiple times to read the required data.

Use the no-tracking method to query when Entity Framework Core accesses data for read-only purposes. EF Core can return the results of no-tracking queries more efficiently.

Use filters and aggregate LINQ queries (for example, .where, .Select, or .Sum statements) so that the database performs filtering to improve performance.

Consider that EF Core may parse some query operators on the client side, which can lead to inefficient query execution. For more information, see performance issues related to client computing.

Do not use mapping queries on collections, which results in the execution of "N + 1" SQL queries. For more information, see optimizing subqueries.

Refer to the EF High performance topic for possible ways to improve application performance:

DbContext Pool

Explicitly compiled query

Before the code is submitted, we recommend that you evaluate the impact of the above high-performance approach. The additional complexity of compiling queries may not necessarily guarantee improved performance.

You can detect query problems by using Application Insights or by using analysis tools to see how long it takes to access the data. Most databases also provide statistics about frequently executed queries, which can also be used as an important reference.

Establish HTTP connection pool through HttpClientFactory

Although HttpClient implements the IDisposable interface, it is actually designed to reuse a single instance. Closing the HttpClient instance causes the socket to open in the TIME_WAIT state for a short period of time. If you create and release HttpClient objects frequently, your application may run out of available sockets. In ASP.NET Core 2.1, HttpClientFactory was introduced as a solution to this problem. It optimizes performance and reliability by pooling HTTP connections.

Recommendations:

Do not create and release HttpClient instances directly.

To use HttpClientFactory to get the HttpClient instance. For more information, see using HttpClientFactory to implement resilient HTTP requests.

Make sure the common code path is as fast as a falcon

If you want all the code to be high-speed, the code path of high-frequency calls is the most critical path for optimization. Optimization measures include:

Consider optimizing the Middleware in the application request processing pipeline, especially the Middleware that runs higher in the pipeline. These components have a significant impact on performance.

Consider optimizing code that is executed for each request or multiple times for each request. For example, custom logs, authentication and authorization, or the creation of transient services, etc.

Recommendations:

Do not use custom middleware to run long tasks.

Use a performance analysis tool such as Visual Studio Diagnostic Tools or PerfView to locate the code hotspot path.

Run long-term tasks outside of HTTP requests

Most requests to ASP.NET Core applications can be handled by the controller or page model that invokes the service and return a HTTP response. For some requests involving long-running tasks, it is best to make the entire request-response process asynchronous.

Recommendations:

Do not complete long-running tasks as part of normal HTTP request processing.

Consider using background services or Azure Function to handle long-running tasks. Performing tasks outside the application is particularly beneficial to the performance of CPU-intensive tasks.

Use real-time communication, such as SignalR, to communicate with the client asynchronously.

Shrink client resources

Complex ASP.NET Core applications often contain front-end files such as JavaScript, CSS, or image files. You can optimize the performance of the initial request in the following ways:

Package to merge multiple files into a single file.

Compress to reduce the file size by removing spaces and comments.

Recommendations:

You want to use ASP.NET Core's built-in support for packaging and compressing client resource files.

Consider other third-party tools, such as Webpack, for complex customer asset management.

Compress Http response

Reducing the size of the response usually significantly improves the responsiveness of the application. One way to reduce the size of the content is to compress the application's response. For more information, see response Compression.

Use the latest ASP.NET Core distribution

Each new release of ASP.NET Core includes performance improvements. The optimizations in .NET Core and ASP.NET Core mean that newer versions are generally superior to older versions. For example, .NET Core 2.1added support for precompiled regular expressions and improved performance from using Span. ASP.NET Core 2.2 adds support for HTTP/2. ASP.NET Core 3.0 adds many improvements to reduce memory usage and increase throughput. If performance is a priority, upgrade to the current version of ASP.NET Core.

Minimize anomalies

There should be few anomalies. Throwing and catching exceptions are slow compared to normal code flow. Therefore, exceptions should not be used to control normal program flow.

Recommendations:

Do not use throwing or catching exceptions as a means of normal program flow, especially in code hotspot paths.

You want to include logic in your application to detect and handle the resulting exception.

To throw or catch an exception for unexpected execution.

Application diagnostic tools, such as Application Insights, can help identify common exceptions in applications that may affect performance.

Performance and reliability

Common performance tips and solutions to known reliability problems are provided below.

Avoid synchronous reads or writes on HttpRequest/HttpResponse body

All of the ASP.NET Core O's are asynchronous. The server implements the Stream interface, which has both synchronous and asynchronous method overloads. Asynchronous methods should be preferred to avoid blocking thread pool threads. Blocking threads can lead to thread pool hunger.

Do not use the following actions: https://docs.microsoft.com/en-us/dotnet/api/System.IO.StreamReader.ReadToEnd. It prevents the current thread from waiting for the result. This is an example of sync over async.

Public class BadStreamReaderController: Controller {[HttpGet ("/ contoso")] public ActionResult Get () {var json = new StreamReader (Request.Body) .ReadToEnd (); return JsonSerializer.Deserialize (json);}}

In the above code, Get reads the entire HTTP request body into memory synchronously. If the client uploads data slowly, the application will have operations that appear to be asynchronous and actually synchronous. The application appears to be asynchronous and actually synchronous because Kestrel does not support synchronous reads.

You should do the following: https://docs.microsoft.com/en-us/dotnet/api/System.IO.StreamReader.ReadToEndAsync, which does not block threads when reading.

Public class GoodStreamReaderController: Controller {[HttpGet ("/ contoso")] public async Task Get () {var json = await new StreamReader (Request.Body) .ReadToEndAsync (); return JsonSerializer.Deserialize (json);}}

The above code asynchronously reads the entire HTTP request body into memory.

[! WARNING] if the request is large, reading the entire HTTP request body into memory may result in insufficient memory (OOM). OOM can cause applications to crash. For more information, see avoid reading large request or response subjects into memory.

You should do the following: complete the request body operation without buffering:

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

The above code serializes the request body into a C # object asynchronously.

ReadFormAsync of Request.Form is preferred.

You should use HttpContext.Request.ReadFormAsync instead of HttpContext.Request.Form. HttpContext.Request.Form can only be used safely in the following scenarios.

The form has been called by ReadFormAsync, and

The data has been read from HttpContext.Request.Form and cached

Do not use the following actions: for example, use HttpContext.Request.Form in the following ways. HttpContext.Request.Form uses sync over async, which leads to thread hunger.

Public class BadReadController: Controller {[HttpPost ("/ form-body")] public IActionResult Post () {var form = HttpContext.Request.Form; Process (form ["id"], form ["name"]); return Accepted ();}

You should use the following actions: use HttpContext.Request.ReadFormAsync to read the form body asynchronously.

Public class GoodReadController: Controller {[HttpPost ("/ form-body")] public async Task Post () {var form = await HttpContext.Request.ReadFormAsync (); Process (form ["id"], form ["name"]); return Accepted ();} avoid reading large request body or response body into memory

In .NET, objects greater than 85 KB are allocated to the large object heap (LOH). Large objects are expensive and include two aspects:

When allocating large object memory, the allocated memory needs to be emptied, which is expensive. CLR ensures that the memory of all newly allocated objects is cleared. (set all memory to 0)

LOH will only be recycled when there is insufficient memory left. LOH needs to be recycled in full garbage collection or Gen2 collection.

This blog post describes the problem well:

When a large object is allocated, it is marked as a Gen 2 object. It's not a small object like Gen 0. As a result, if you run out of memory when using LOH, GC clears the entire managed heap, not just the LOH part. Therefore, it will clean 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, this is acceptable. However, it is definitely not suitable for high-performance Web servers, because high-performance Web servers need more memory for processing regular Web requests (reading from sockets, decompressing, decoding JSON, and so on).

Naively store a large request or response body in a single byte [] or string:

This may result in a rapid depletion of the remaining space in LOH.

The resulting full GC may cause performance problems for the application.

Using synchronous API to process data

For example, when using a serializer / deserializer that only supports synchronous reads and writes (for example, JSON.NET):

The data is buffered asynchronously into memory and passed to the serializer / deserializer.

[! WARNING] if the request is large, it may result in out of memory (OOM). OOM can cause applications to crash. For more information, see avoid reading large request or response subjects into memory.

ASP.NET Core 3.0 uses https://docs.microsoft.com/en-us/dotnet/api/system.text.json for JSON serialization by default, which brings the following benefits. Https://docs.microsoft.com/en-us/dotnet/api/system.text.json:

Read and write JSON asynchronously.

Optimized for UTF-8 text.

Generally higher performance than Newtonsoft.Json.

Do not store IHttpContextAccessor.HttpContext in a field

IHttpContextAccessor.HttpContext returns HttpContext. Exe in the current request thread. IHttpContextAccessor.HttpContext** should not be stored in a field or variable.

Do not use actions such as storing HttpContext in a field, and then using that field 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");}

The above code often gets Null or incorrect HttpContext in the constructor.

You should do the following:

Save the https://docs.microsoft.com/en-us/aspnet/core/Microsoft.AspNetCore.Http.IHttpContextAccessor?view=aspnetcore-3.1 in the field.

Get and use HttpContext at the right time and check to see if it is 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");}} do not try to use HttpContext in multithreading

HttpContext is not thread safe. Accessing HttpContext in parallel from multiple threads can lead to unexpected behavior, such as thread hangs, crashes, and data corruption.

Do not use the following example to make three parallel requests and record the incoming request path before and after the HTTP request. The request path will be accessed by multiple threads (possibly in parallel).

Public class AsyncBadSearchController: Controller {[HttpGet ("/ search")] public async Task 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 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;}

You should do this: the following example copies the data you need to use below from the incoming request before making three parallel requests.

Public class AsyncGoodSearchController: Controller {[HttpGet ("/ search")] public async Task 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 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;} do not use HttpContext after request processing is complete

HttpContext is available only if the ASP.NET Core pipeline processes active HTTP requests. The entire ASP.NET Core pipeline is a chain of calls made up of asynchronous proxies that are used to process each request. When the Task completes from the call chain and returns, the HttpContext is recycled.

Do not do the following: the following example uses async void, which causes the HTTP request to be processed on the first await, which results in:

In ASP.NET Core applications, this is a completely wrong approach

Access the HttpResponse after the HTTP request is completed.

The process crashed.

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");}}

You should do the following: the following example returns the Task to the framework, so the HTTP request will not complete until the operation is complete.

Public class AsyncGoodTaskController: Controller {[HttpGet ("/ async")] public async Task Get () {await Task.Delay (1000); await Response.WriteAsync ("Hello World");}} do not use HttpContext in background threads

Do not use the following: the following example uses a closure to read HttpContext from the Controller property. This is a mistake because it will lead to:

The code runs outside the scope of the Http request.

Attempted 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 ();}

You should do the following:

During the request processing phase, all the data needed by the background thread is copied.

Do not use all references to 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 are best operated by managed services. For more information, see running background tasks with managed Services.

Do not get the service injected into controller in the background thread

Do not do this: the following example uses closures to get DbContext from controller. This is a mistake. This will cause the code cloud to be outside the scope of the request. ContocoDbContext, on the other hand, is based on the request scope, so this raises 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 ();}

You should do the following:

Inject https://docs.microsoft.com/en-us/aspnet/core/Microsoft.Extensions.DependencyInjection.IServiceScopeFactory?view=aspnetcore-3.1 and create a new scope in the background thread. IServiceScopeFactory is a singleton object, so there is no problem with this.

Create a new scope injection dependent service in the background thread.

Do not quote everything from controller

Do not read ContocoDbContext from the 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 (); context.Contoso.Add (new Contoso ()) Await context.SaveChangesAsync (); return Accepted ();}}

The following highlighted code description:

Create a new scope for background operations and get the required services from it.

Use ContocoDbContext in the correct scope, that is, the object can only be used in the request 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 (); context.Contoso.Add (new Contoso ()) Await context.SaveChangesAsync ();}}); return Accepted ();} do not try to modify status code or header when the response body has started to be sent

ASP.NET Core does not buffer the HTTP response body. Once the text starts to be sent:

The Header is sent to the client along with the packet of the body.

The header cannot be modified at this time.

Do not use the following: the following code attempts to add a response header after the response starts:

App.Use (async (context, next) = > {await next (); context.Response.Headers ["test"] = "test value";})

In the above code, if next () has started writing the response, then context.Response.Headers ["test"] = "test value"; will throw an exception.

You should do the following: the following example checks whether the HTTP response was started before the Header was modified.

App.Use (async (context, next) = > {await next (); if (! context.Response.HasStarted) {context.Response.Headers ["test"] = "test value";}})

You should do the following: the following example uses HttpResponse.OnStarting to set up the Header so that the Header can be written to the client at one time when the response starts.

In this way, the response header invokes the registered callback at the beginning of the response for an one-time write. This and this will do:

Modify or overwrite the response header at the appropriate time.

You don't need to know the behavior 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 ();}); do not call next () if you have started writing the response body

They are called only when subsequent components can handle responses or, so if the response body is currently being written, subsequent operations are no longer needed and it is possible to throw an exception.

In-process mode should be used when hosted on IIS

Using in-process mode hosting, the ASP.NET Core application will run in the same process as the IIS worker process. The In-process pattern has better performance than out-of-process because it does not require proxy forwarding of requests through the loopback network adapter. A loopback network adapter is a network adapter that redirects network traffic sent by this machine back to this machine. IIS process management is done by Windows Process Activation Service (WAS).

The default in ASP.NET Core 3.0 and later will be hosted in in-process mode.

At this point, I believe you have a deeper understanding of "what is the method of ASP.NET Core performance optimization". You might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report