Best Practices for 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 block asynchronous execution by calling Task.Wait or Task<TResult>.Result
  •        Do not acquire locks in common code paths. 
  •       Do not call Task.Run and immediately await it - ASP.NET Core already runs app code on normal Thread Pool threads, so calling Task.Run only results in extra unnecessary Thread Pool scheduling. 
  •      Do make hot code paths asynchronous.
  •      Do call data access, I/O, and long-running operations APIs asynchronously if an asynchronous API is available.
  •     Do not use Task.Run to make a synchronous API asynchronous.
  •     Do make controller/Razor Page actions asynchronous. The entire call stack is asynchronous in order to benefit from async/await patterns.

Always avoid to load large amount of data in a webpage. During returning a collection of objects, consider whether it could lead to performance issues. Following performance issue could be outcome.

  •         OutOfMemoryException or high memory consumption
  •         Thread pool starvation (see the following remarks on IAsyncEnumerable<T>)
  •         Slow response times
  •         Frequent garbage collection

 

Try to return large collections towards multiple smaller pages - add pagination to mitigate the preceding scenarios by using page size and page index parameters.

Garbage collection is especially expensive on large objects (> 85 K bytes). Large objects are stored on the large object heap and require a full (generation 2) garbage collection to clean up and this requires a temporary suspension of app execution. Frequent allocation and de-allocation of large objects can cause inconsistent performance.

·         Do consider caching large objects that are frequently used. Caching large objects prevents expensive allocations.

·         Do pool buffers by using an ArrayPool<T> to store large arrays.

·         Do not allocate many, short-lived large objects on hot code paths.

 

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. To avoid synchronous enumeration, use ToListAsync before returning the enumerable. Beginning with ASP.NET Core 3.0, IAsyncEnumerable<T> can be used as an alternative to IEnumerable<T> that enumerates asynchronously.

·         Use IAsyncEnumerable<T> instead of IEnumerable<T>

Optimize data access and I/O

·         Do call all data access APIs asynchronously.

·         Do not retrieve more data than is necessary. 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. Depending on the scenario, use a MemoryCache or a DistributedCache.

·         Do minimize network round trips. The goal is to retrieve the required data in a single call rather than several calls.

·         Do use no-tracking queries in Entity Framework Core when accessing data for read-only purposes. EF Core can return the results of no-tracking queries more efficiently.

·         Do filter and aggregate LINQ queries (with .Where.Select, or .Sum statements, for example) so that the filtering is performed by the database.

·         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.

·         Do not use projection queries on collections, which can result in executing "N + 1" SQL queries. For more information, see Optimization of correlated subqueries.

 

The following approaches may improve performance in high-scale apps:

·         DbContext pooling

·         Explicitly compiled queries

 

Why we need to use HttpClientFactory

The original and well-known issue while using HttpClient class it leave sockets open in the TIME_WAIT state even after object dispose.

Though HttpClient class implements IDisposable, when the HttpClient object gets disposed of, the underlying socket is not immediately released, which can lead to a socket exhaustion problem. So, if your code creates and disposes of HttpClient objects frequently, then the app may exhaust available sockets.

Possible approaches to solve that problem are based on the creation of the HttpClient object as singleton or static. But while developers start using a shared instance of HttpClient in long-running processes where the HttpClient is instantiated as a singleton or a static object, it fails to handle the DNS changes.


To address the issues mentioned above, .NET Core 2.1 introduced two approaches, one of them being IHttpClientFactory. It's an interface that's used to configure and create HttpClient instances in an app through Dependency Injection (DI). It also provides extensions for Polly-based middleware to take advantage of delegating handlers in HttpClient.

 

The alternative is to use SocketsHttpHandler with configured PooledConnectionLifetime. This approach is applied to long-lived, static or singleton HttpClient instances. 

Recommendations:

·         Do not create and dispose of HttpClient instances directly.

·         Do use HttpClientFactory to retrieve HttpClient instances. For more information, see Use HttpClientFactory to implement resilient HTTP requests.

Comments

Popular posts from this blog

How to fix Azure DevOps error MSB4126

How to create Custom Visuals in Power BI – Initial few Steps

How to fix Azure DevOps Error CS0579