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:
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.
- Do not use custom middleware
components with long-running tasks.
- Do use performance profiling
tools, such as Visual
Studio Diagnostic Tools or PerfView), to identify hot
code paths.
Comments
Post a Comment