今天这篇文章我们来聊一聊如何提升并优化 ASP.NET Core 应用程序的性能, 本文的大部分内容来自翻译, 当然中间穿插着自己的理解, 希望对大家有所帮助! 话不多说开始今天的主题吧!
我们都知道性能是公共网站取得成功的关键因素之一. 如果一个网站的响应时间超过 3 秒, 那么用户通常不会再此光顾(此网站). 谷歌, Bing, 百度以及其他搜索引擎也更倾向于推荐优化后的, 移动友好的以及响应速度更快的网站.
大部分内容翻译自: https://www.c-sharpcorner.com/article/10-tips-to-improve-performance-of-asp-net-core-application/
这里我们举一个例子: 我们有多个搜索引擎, 如 Google,Bing, 百度, 搜狗等等; 然而, 我们更喜欢 Google 或 Bing, 因为这些搜索引擎速度非常快, 可以在 3-4 秒内获得结果. 如果这些搜索引擎的响应速度超过 10 秒, 你还会使用它们吗? 我认为大伙应该不会用了吧. 如今的用户最不能容忍的想必就是等待了吧.
今天, 我们将学习一些有助于提高 ASP.NET Core 网站性能的一些小技巧. 希望大家能够有所收获.
我们都知道 ASP.NET Core 是微软提供的一个免费的, 开源的, 跨平台的 web 开发框架. 它不是 ASP.NET 的升级版本, 但它是一个从头开始完全重写的框架, 它附带了 ASP.NET MVC 和 ASP.NET Web API 的单一编程模型.
在这里, 我不打算讨论 ASP.NET Core 及其特性. 如果您是 ASP.NET Core 的新手, 您可以阅读我的 ASP.NET Core 实战教程《.NET Core 实战项目之 CMS 第一章 入门篇 - 开篇及总体规划》
下面我们就开始今天的主题, 如何提升 ASP.NET Core 应用程序的性能的技巧开始吧.
始终使用 ASP.NET Core 的最新版本
ASP.NET Core 的第一个版本是在 2016 年与 VisualStudio 2015 一起发布的, 现在我们有了 ASP.NET Core3.0, 每一个新版本都越来越好. 最新的 ASP.NET Core 3.0 的主要更新如下:
Razor 组件的改进. 现在 2 个项目合并成单个项目模板, Razor 组件支持端点路由和预渲染, Razor 组件可以托管在 Razor 类库中. 还改进了事件处理和表单和验证支持.
运行时编译. 它在 ASP.NET Core 3.0 模板中被禁用, 但现在可以通过向项目添加特殊的 NuGet 包来打开它.
Worker Service 模板. 需要编写 Windows 服务还是 Linux 守护进程? 现在我们有了 Worker Service 模板.
gRPC 模板. 与谷歌一起构建的 gRPC 是一种流行的远程过程调用 (RPC) 框架. 此版本的 ASP.NET Core 在 ASP.NET Core 上引入了第一等的 gRPC 支持.
Angular 模板使用 Angular 7. Angular SPA 模板现在使用 Angular 7, 在第一次稳定释放之前, 它将被 Angular 8 替换.
SPA-s 的身份验证. Microsoft 通过此预览为单页应用程序添加了现成的身份验证支持.
SignalR 与端点路由集成. 小变化 - 现在使用端点路由定义 SingalR 路由.
SignalR Java 客户端支持长轮询. 即使在不支持或不允许 WebSocket 的环境中, SignalR Java 客户端现在也可以使用.
友情提示: 在构建新的 ASP.NET Core 项目时, 不要忘记选择最新版本. VisualStudio 2019 预览版现在已经支持 ASP.NET Core 3.0 了.
避免任何层的同步调用
在开发 ASP.NET Core 应用程序时, 尽量避免创建阻塞的调用. 阻塞调用是指当前请求未完成之前会一直阻止下一个执行的调用. 阻塞调用或同步调用可以是任何东西, 可以是从 API 中获取数据, 也可以是执行一些内部操作. 您应该始终以异步方式执行调用.
始终使用异步编程(ASYNC-AWAIT)
异步编程模型是在 C#5.0 中引入的, 并变得非常流行. ASP.NET Core 使用相同的异步编程范例来使应用程序更可靠, 更快和更稳定.
您应该在代码中使用端到端异步编程.
让我们举一个例子; 我们有一个 ASP.NET CoreMVC 应用程序, 中间有一些数据库的操作. 正如我们所知道的, 它可能有很多分层结构, 这都取决于用户的项目架构, 但是让我们举一个简单的例子, 其中我们有 Controller》Repository 层等等. 让我们看看如何在控制器层编写示例代码.
- [HttpGet]
- [Route("GetPosts")]
- public async Task GetPosts()
- {
- try
- {
- var posts = await postRepository.GetPosts();
- if (posts == null)
- {
- return NotFound();
- }
- return Ok(posts);
- }
- catch (Exception)
- {
- return BadRequest();
- }
- }
接下来的代码然是了我们如何在 repository 层实现异步编程.
- public async Task<List<PostViewModel>> GetPosts()
- {
- if (db != null)
- {
- return await (from p in db.Post
- from c in db.Category
- where p.CategoryId == c.Id
- select new PostViewModel
- {
- PostId = p.PostId,
- Title = p.Title,
- Description = p.Description,
- CategoryId = p.CategoryId,
- CategoryName = c.Name,
- CreatedDate = p.CreatedDate
- }).ToListAsync();
- }
- return null;
- }
使用异步编程避免 TASK.WAIT 或 TAST.RESULT
在使用异步编程时, 我建议您避免使用 Task.Wait 和 Task.Result 并尝试使用 WAIT, 原因如下:
它们阻塞线程直到任务完成, 并等待任务完成. 等待同步阻塞线程, 直到任务完成.
Wait 和 Task.Result 在 AggregateException 中包含所有类型的异常, 并在在执行异常处理时增加复杂性. 如果您使用的是等待 await 而不是 Task.Wait 和 Task.Result 的话, 那么您就不必担心异常的处理了.
有时, 它们都会阻塞当前线程并创建死锁.
只有在并行任务执行正在进行时才能使用 Wait 和 Task.Result . 我们建议您不要在异步编程中使用它.
下面让我们分别演示下正确使用以及不建议使用 Task.Wait 的例子, 来加深理解吧!
- // 正确的例子
- Task task = DoWork();
- await task;
- // 不建议使用的例子
- Task task = DoWork();
- task.Wait();
下面让我们分别演示下正确使用以及不规范使用 Task.Result 的例子, 来加深理解吧!
- // Good Performance on UI
- Task<string> task = GetEmployeeName();
- txtEmployeeName.Text = await task;
- // Bad Performance on UI
- Task<string> task = GetEmployeeName();
- txtEmployeeName.Text = task.Result;
了解更多关于异步编程的最佳实践.
异步执行 I/O 操作
在执行 I/O 操作时, 您应该异步执行它们, 这样就不会影响其他进程. I/O 操作意味着对文件执行一些操作, 比如上传或检索文件. 它可以是任何操作如: 图像上传, 文件上传或其他任何操作. 如果您试图以同步的方式完成它, 那么它会阻塞主线程并停止其他后台执行, 直到 I/O 完成为止. 因此, 从提升性能上来说, 您在对 I/O 进行操作时应该始终进行异步执行.
我们有很多异步方法可用于 I/O 操作, 如 ReadAsync,WriteAsync,FlushAysnc 等. 下面是一个简单的例子, 说明我们如何异步创建一个文件的副本.
- public async void CreateCopyOfFile()
- {
- string dir = @"c:\Mukesh\files\";
- using (StreamReader objStreamReader= File.OpenText(dir + "test.txt"))
- {
- using (StreamWriter objStreamWriter= File.CreateText(dir+ "copy_test.txt"))
- {
- await CopyFileToTarget(objStreamReader, objStreamWriter);
- }
- }
- }
- public async Task CopyFileToTarget(StreamReader objStreamReader, StreamWriter objStreamWriter)
- {
- int num;
- char[] buffer = new char[0x1000];
- while ((num= await objStreamReader.ReadAsync(buffer, 0, buffer.Length)) != 0)
- {
- await objStreamWriter.WriteAsync(buffer, 0, num);
- }
- }
总是使用缓存
如果我们能在每次执行的时候减少减少对服务器的请求次数, 那么我们就可以提高应用程序的性能. 这并不意味着您执行的时候不会请求服务器, 而是意味着您不会每次执行都请求服务器. 第一次, 您将请求服务器并获得响应, 此响应将在某个地方存储一段时间(将有一些到期), 下一次当您对相同的响应进行调用时, 您将首先检查您是否已经在第一个请求中获得了数据并存储在某个地方, 如果是的话, 您将检查是否已经获得了数据. 使用存储的数据, 而不是调用服务器.
将数据保存在某个位置并让下次请求从这个地方获取数据而不是从服务器获取是一种很好的做法. 在这里, 我们可以使用缓存. 缓存内容有助于我们再次减少服务器调用, 并帮助我们提高应用程序的性能. 我们可以在客户端缓存, 服务器端缓存或客户机 / 服务器端缓存等位置的任意点执行缓存.
我们可以在 ASP.NET Core 中使用不同类型的缓存, 比如我们可以在内存中进行缓存, 也可以使用响应缓存, 也可以使用分布式缓存. 更多关于 ASP.NET Core 中的缓存
- public async Task GetCacheData()
- {
- var cacheEntry = await
- _cache.GetOrCreateAsync(CacheKeys.Entry, entry =>
- {
- entry.SlidingExpiration = TimeSpan.FromSeconds(120);
- return Task.FromResult(DateTime.Now);
- });
- return View("Cache", cacheEntry);
- }
优化数据访问
我们还可以通过优化数据访问逻辑, 数据库表和查询来提高应用程序的性能. 众所周知, 大多数应用程序都使用某种数据库, 每次从数据库获取数据时, 都会影响应用程序的性能. 如果数据库加载缓慢, 则整个应用程序将缓慢运行. 这里我们有一些建议:
减少 HTTP 请求的次数, 意味着您应该始终尝试减少网络往返次数.
试着一次得到所有的数据. 这意味着不对服务器进行多次调用, 只需进行一两次调用就可以带来所有所需的数据.
经常对不经常变化的数据设置缓存.
不要试图提前获取不需要的数据, 这会增加响应的负载, 并导致应用程序的加载速度变慢.
优化自定义代码
除了业务逻辑和数据访问代码之外, 应用程序中可能还有一些自定义代码. 确保此代码也是优化的. 这里有一些建议:
应该优化对每个请求执行的自定义日志记录, 身份验证或某些自定义处理程序的代码.
不要在业务逻辑层或中间件中执行长时间运行的代码, 它会阻塞到服务器的请求, 从而导致应用程序需要很长时间才能获得数据. 您应该在客户端或数据库端为此进行优化代码.
始终检查长期运行的任务是否应该异步执行, 而不影响其他进程.
您可以使用实时客户端 - 服务器通信框架, 如: SignalR, 来进行异步工作.
Entity Framework Core 的查询优化
众所周知, EF Core 是一个面向. NET 开发人员的 ORM, 它帮助我们处理数据库对象, 而不像往常那样编写大量代码. 它帮助我们使用模型的数据库. 数据访问逻辑代码在性能上起着至关重要的作用. 如果您的代码没有优化, 那么应用程序的性能通常就不会很好.
但是, 如果您在 EFCore 中以优化的方式编写数据访问逻辑, 那么肯定会提高应用程序的性能. 在这里, 我们有一些技巧来提高性能.
在获取只是用来只读显示的数据时不使用跟踪. 它提高了性能.
尝试在数据库端过滤数据, 不要使用查询获取整个数据, 然后在您的末尾进行筛选. 您可以使用 EF Core 中的一些可用功能, 可以帮助您在数据库端筛选数据的操作, 如: WHERE,Select 等.
使用 Take 和 Skip 来获取我们所必须要显示的数量的记录. 这里可以举一个分页的例子, 在这个例子中, 您可以在单击页码的同时使用 Take 和 Skip 来获取当前页面的数据.
让我们以一个例子为例, 了解如何使用 Select 和 AsNoTracking 优化 EF Core 的查询.
- public async Task<PaginatedList> GetPagedPendingPosts(int pageIndex, int pageSize, List allowedCategories)
- {
- var allowedCatIds = allowedCategories.Select(x => x.Id);
- var query = _context.Post
- .Include(x => x.Topic.Category)
- .Include(x => x.User)
- .Where(x => x.Pending == true && allowedCatIds.Contains(x.Topic.Category.Id))
- .OrderBy(x => x.DateCreated);
- return await PaginatedList.CreateAsync(query.AsNoTracking(), pageIndex, pageSize);
- }
其他一些提示
这里我们还有一些其他性能改进的东西可以在 ASP.NET Core 应用程序中进行实现.
编写优化和测试代码. 您还可以使用来自专业高级开发者的代码示例, 包括产品文档. 产品团队编写的代码 (如 C# 团队) 通常是优化的, 现代化的, 并且遵循最佳实践.
使用经过优化和良好测试的 API 和库. 例如, 在某些情况下, ADO.NET 可能是比 Entity Framework 或其他 ORM 库更好的选择.
如果您需要下载一个很大的文件的话, 您可能需要考虑使用压缩算法. 这里有几个内置的压缩库, 如 Gzip 和 Brotli.
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddResponseCompression();
- services.Configure(options =>
- {
- options.Level = CompressionLevel.Fastest;
- });
- }
附加的建议(面向 Client)
我想分享一些面向客户端的提升性能的技巧. 如果您正在使用 ASP.NET Core MVC 创建网站, 下面是一些提示:
捆绑和小型化
使用捆绑和小型化可以减少服务器请求次数. 尝试一次加载所有客户端资源, 如样式, JS/CSS. 您可以首先使用小型化缩小文件, 然后将这些文件打包到一个文件中, 这将加快加载速度并减少 HTTP 请求的数量.
最后加载 JavaScript
您应该始终尝试在页面尾部加载 JavaScript 文件, 除非在此之前需要使用它们. 如果您这样做, 您的网站将显示的更快, 并且用户也不需要等待并看到这些内容.
压缩图像
确保使用压缩技术缩小图像的大小.
使用 CDN
如果您只有几个样式和 JS 文件, 那么可以从您的服务器加载. 对于较大的静态文件, 请尝试使用 CDN.CDN 通常可以在多个位置上使用, 并且文件是从本地服务器提供的. 从本地服务器加载文件可以提高网站性能.
最后
来源: https://www.cnblogs.com/yilezhu/p/10507984.html