10 个提升 ASP.NET Core 应用程序性能的技巧






3.88/5 (16投票s)
在这篇文章中,我们将学习一些可以在开发 ASP.NET Core 网站时实现的要点,以提高性能。
引言
性能是考虑任何网站成功与否时的主要关注点。我们有成千上万个提供类似功能的网站,但并非所有都受欢迎。在这成千上万的网站中,有些很受欢迎,而那些网站拥有高度优化的数据、安全性和良好的性能。我们总是倾向于使用那些在最短时间内即可访问的网站。
举个例子,我们有多个搜索引擎,如 Google、Bing、Baidu、Ask.com 等等。但你们都知道,我们更喜欢 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 应用程序性能改进的技巧。
1. 始终使用最新版本的 ASP.NET Core
ASP.NET Core 的第一个版本于 2016 年随 Visual Studio 2015 发布,现在我们有了 ASP.NET Core 2.2 版本。如果您比较从第一个版本到当前版本的性能改进,您会发现使用 ASP.NET Core 2.2(当前最新版本)开发的应用程序比以前的版本运行速度快得多。微软在后续版本中总会带来一些性能改进。
因此,在使用 ASP.NET Core 创建应用程序时,请始终优先使用最新版本的 ASP.NET Core。使用最新版本,您一定会发现性能有所提升。ASP.NET Core 的下一个版本是 3.0,目前正在开发中,有望很快随 Visual Studio 2019 发布。
2. 避免任何级别的同步调用
在开发 ASP.NET Core 应用程序时,请尽量避免创建阻塞调用。阻塞调用是指在完成之前会阻止下一个执行的调用。阻塞调用或同步调用可以是任何东西,无论是从 API 获取数据还是在内部执行某些操作。您应该始终以异步方式执行调用。
3. 始终使用异步编程 (Async-Await)
异步编程模型在 C# 5.0 中引入,并变得非常流行。ASP.NET Core 使用相同的异步编程范式来使应用程序更可靠、更快、响应更灵敏。
在编写代码时,您应该使用端到端的异步编程。让我们举一个例子,我们有一个带有数据库实现的 ASP.NET Core MVC 应用程序。我们知道,它可能有很多层,这取决于用户的项目架构,但以一个简单的例子为例,我们有Controller > Repository Layer 等。让我们看看如何在控制器级别编写示例代码。
[HttpGet]
[Route("GetPosts")]  
public async Task < IActionResult > GetPosts()
{
  try {
    var posts = await postRepository.GetPosts();
    if (posts == null) {
      return NotFound();
    }
    return Ok(posts);
  }
  catch (Exception) {
    return BadRequest();
  }
} 
这就是我们在存储库级别实现异步编程的方式。
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;
}
4. 避免在异步编程中使用 Task.Wait 或 Task.Result
在使用异步编程时,我们建议您避免使用 Task.Wait 和 Task.Result,而尝试使用 await,原因如下:
- 它们会阻塞线程直到任务完成,并等待任务完成。Wait会同步阻塞线程直到任务完成。
- Wait和- Task.Result都会将任何类型的异常包装在- AggregateException中,从而增加异常处理的复杂性。如果您使用- await而不是- Task.Wait和- Task.Result,那么您就不必担心异常处理了。
- 有时,这两者都会阻塞当前线程并导致死锁。
- Wait和- Task.Result只能在并行任务执行时使用。我们建议不要将其与异步编程一起使用。
让我们通过以下方式理解 Task.Wait 的好坏示例。
// Good Performance  
Task task = DoWork();
await task;
// Bad Performance  
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;
了解更多关于异步编程最佳实践的信息。
5. 异步执行 I/O 操作
在执行 I/O 操作时,您应该异步执行,而不会影响其他进程。I/O 操作是指执行涉及文件的一些操作,如上传或检索文件。它可以是任何东西,如图片上传、文件上传或其他任何东西。如果您尝试以同步方式完成它,它就会阻塞主线程并停止其他后台执行,直到 I/O 完成。因此,从性能角度来看,您应该始终对 I/O 操作使用异步执行。
我们有许多可用于 I/O 操作的 Async 方法,如 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);
  }
}
6. 始终使用缓存
如果我们能减少每次向服务器发出的请求数量,就可以提高应用程序的性能。这并不意味着您不再向服务器发出请求,它只意味着您不必每次都向服务器发出请求。第一次,您将向服务器发出请求并获得响应,然后将此响应存储在某个地方以备将来使用(会有一些过期时间),下次,当您请求相同的响应时,首先,您将检查是否已经从第一次请求中获得了数据并存储在某个地方,如果是,那么您将使用存储的数据,而不是向服务器发出请求。
将数据保存在我们可以频繁访问而无需向服务器发出的位置是一个好习惯。在这里,我们可以使用Cache。缓存内容有助于我们避免重复向服务器发出请求,并有助于提高应用程序的性能。我们可以在任何位置进行缓存,例如客户端缓存、服务器端缓存或客户端/服务器端缓存。
ASP.NET Core 提供了各种缓存机制,如内存缓存、响应缓存或分布式缓存。更多关于Asp.Net Core 缓存的信息。
public async Task < IActionResult > GetCacheData()
{
  var cacheEntry = await
  _cache.GetOrCreateAsync(CacheKeys.Entry, entry => {
    entry.SlidingExpiration = TimeSpan.FromSeconds(120);
    return Task.FromResult(DateTime.Now);
  });
  return View("Cache", cacheEntry);
}  
7. 优化数据访问
我们还可以通过优化数据访问逻辑来提高应用程序的性能。众所周知,大多数应用程序完全依赖于数据库,并且我们每次都需要从数据库获取数据并在 UI 上显示。如果这很耗时,那么应用程序加载的时间就会很长。这里有一些技术可以在编写代码时实现,以大大提高性能。
- 减少 HTTP 调用次数,意味着您应始终尝试减少网络往返次数。
- 尝试一次性获取所有数据。这意味着与其发出多个服务器请求,不如只发出一个或两个请求即可获取所有必需的数据。
- 对于不经常更改的数据,请始终进行缓存。
- 不要提前获取不需要的数据,这会增加响应负载,并导致应用程序加载速度变慢。
8. 优化自定义代码
除了优化数据访问逻辑外,我们还可以优化业务逻辑或中间件自定义代码。优化或重构这些代码有助于提高应用程序的性能。这里有一些我们应该关注的要点。
- 自定义日志记录、身份验证或某些在每次请求时执行的自定义处理程序代码都应进行优化。
- 不要在业务逻辑层或中间件中执行自定义的长时间运行的自定义执行,这基本上会阻塞请求到达服务器,导致应用程序花费大量时间获取数据。您应该有针对这种情况的优化代码,要么在客户端,要么在数据库端。
- 始终检查长时间运行的任务应异步执行,而不影响其他进程。
- 您可以以 SignalR 为例,它是一种实时客户端-服务器通信,异步工作。
9. Entity Framework Core 查询优化
众所周知,EF Core 是 .NET 开发者的 ORM,它帮助我们不必像往常一样编写大量代码就能操作数据库对象。它帮助我们使用模型来使用数据库。数据访问逻辑代码在性能方面起着至关重要的作用。如果您的代码未得到优化,那么您的应用程序将无法正常运行。
但是,如果您以优化的方式在 EF Core 中编写数据访问逻辑,那么肯定会提高应用程序的性能。这里有一些将提高性能的技术。
- 在仅用于读取数据时使用 No Tracking。这可以提高性能。
- 尝试在数据库端过滤数据,不要先获取所有查询数据,然后再进行过滤。您可以使用 EF Core 中的一些函数,如 Where、Select,它们可以帮助您在数据库端过滤数据。
- 使用 Take和Skip检索所需数量的记录。您可以以分页为例,在单击页码时实现Take和Skip。
 
 让我们举一个例子,尝试理解如何使用Select和AsNoTracking来优化 EF Core 查询。
public async Task < PaginatedList < Post >> GetPagedPendingPosts
     (int pageIndex, int pageSize, List<Category> 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<Post>.CreateAsync(query.AsNoTracking(), pageIndex, pageSize);
}
10. 其他一些技巧
在这里,我们有一些可以在 ASP.NET Core 应用程序中实现的性能改进的补充内容。
- 以一种经过优化和测试且最大限度减少异常发生流程的方式编写逻辑。异常代码执行的次数越少,应用程序的性能就越高。
- 始终尝试使用简单的 NET 代码或像 Dapper 这样高性能的 ORM 来进行数据库操作。因为如果数据库操作执行查询花费的时间过长,它就会降低应用程序的性能。我们都知道,在开发阶段,实现 ADO.NET 代码并进行管理比实现 ORM 所需的时间要长。但同样重要的是,如果您使用简单的 ADO.NET,那么您的应用程序将比使用任何其他 ORM 更快。
- 如果响应大小很大,那么加载它显然需要很长时间。因此,始终使用压缩技术减小响应大小,然后再加载数据。您可以在中间件中实现压缩逻辑。我们有一些压缩提供商,如 Gzip、Brotli。
public void ConfigureServices(IServiceCollection services)
{
  services.AddResponseCompression();
  services.Configure<GzipCompressionProviderOptions>(options => {
    options.Level = CompressionLevel.Fastest;
  });
}
奖励提示(客户端)
我们想分享一些客户端代码的性能技巧。如果您正在使用 ASP.NET Core MVC 创建网站,那么我相信您应该始终遵循这些技巧来使您的网站表现良好。
- 捆绑和小型化
	通过使用捆绑和最小化,我们可以在加载到 UI 之前优化内容。从性能角度来看,始终尝试一次性加载所有客户端资产,如 JS/CSS。这减少了网络请求次数。您可以先使用最小化工具压缩文件,然后将它们捆绑到一个文件中,该文件可以更快地加载并减少资源的 HTTP 请求次数。 
- 最后加载 JavaScript
	您应该始终尝试最后加载 JavaScript 文件。这有很多好处,首先,您的网站将在最短时间内渲染,当您的 JavaScript 执行时,您的 DOM 元素将可用。通过这样做,您可以使您的应用程序更快。 
- 缩小图片
	始终避免加载最大尺寸的图片。加载之前,您可以使用压缩技术缩小图片。 
- 使用 CDN
	如果您有自定义的 CSS 或 JavaScript 文件,那么加载它们是可以的。但如果您正在使用一些有 CDN 版本的第三方库,那么始终尝试使用 CDN 文件路径而不是从您那里下载的库文件路径。这是因为 CDN 有不同的服务器分布在不同的区域,如果您在一个区域使用网站,那么当您访问网站时,您将从您所在区域的服务器获取 CDN 文件,这非常快。 
结论
所以,今天,我们学习了如何提高 ASP.NET Core 应用程序的性能。
我希望这篇文章能对您有所帮助。请在下方使用评论提供反馈,这将帮助我在下一篇文章中改进自己。如果您有任何疑问,请在评论区提问,如果您喜欢这篇文章,请与您的朋友分享。谢谢!

