ASP.NET Core 性能优化最佳实践

本文提供了 ASP.NET Core 的性能最佳实践指南。

译文原文地址:https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-3.1

积极利用缓存

这里有一篇文档在多个部分中讨论了如何积极利用缓存。有关详细信息,请参阅︰ https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-3.1.

了解代码中的热点路径

在本文档中, 代码热点路径 定义为频繁调用的代码路径以及执行时间的大部分时间。代码热点路径通常限制应用程序的扩展和性能,并在本文档的多个部分中进行讨论。

避免阻塞式调用

ASP.NET Core 应用程序应设计为同时处理许多请求。异步 API 可以使用一个小池线程通过非阻塞式调用来处理数以千计的并发请求。线程可以处理另一个请求,而不是等待长时间运行的同步任务完成。

ASP.NET Core 应用程序中的常见性能问题通常是由于那些本可以异步调用但却采用阻塞时调用而导致的。同步阻塞会调用导致 线程池饥饿 和响应时间降级。

不要:

  • 通过调用 Task.Wait 或 Task.Result 来阻止异步执行。

  • 在公共代码路径中加锁。ASP.NET Core 应用程序应设计为并行运行代码,如此才能使得性能最佳。

  • 调用 Task.Run 并立即 await 。ASP.NET Core 本身已经是在线程池线程上运行应用程序代码了,因此这样调用 Task.Run 只会导致额外的不必要的线程池调度。而且即使被调度的代码会阻止线程, Task.Run 也并不能避免这种情况,这样做没有意义。

要:

  • 确保 代码热点路径 全部异步化。

  • 如在进行调用数据读写、I/O 处理和长时间操作的 API 时,存在可用的异步 API。那么务必选择异步 API 。但是,不要 使用 Task.Run 来包装同步 API 使其异步化。

  • 确保 controller/Razor Page actions 异步化。整个调用堆栈是异步的,就可以利用 async/await 模式的性能优势。
    使用性能分析程序 ( 例如 PerfView) 可用于查找频繁添加到 线程池 的线程。 Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start 事件表示新线程被添加到线程池。

使用 IEumerable<T> 或 IAsyncEnumerable<T> 作为返回值

在 Action 中返回 IEumerable<T> 将会被序列化器中进行同步迭代 。结果是可能导致阻塞或者线程池饥饿。想要要避免同步迭代集合,可以在返回迭代集合之前使用 ToListAsync 使其异步化。

从 ASP.NET Core 3.0 开始, IAsyncEnumerable<T> 可以用作为 IEumerable<T> 的替代方法,以异步方式进行迭代。有关更多信息,请参阅 Controller Action 的返回值类型。

尽可能少的使用大对象

.NET Core 垃圾收集器 在 ASP.NET Core 应用程序中起到自动管理内存的分配和释放的作用。自动垃圾回收通常意味着开发者不需要担心如何或何时释放内存。但是,清除未引用的对象将会占用 CPU 时间,因此开发者应最小化 代码热点路径 中的分配的对象。垃圾回收在大对象上代价特大 (> 85 K 字节) 。大对象存储在 large object heap 上,需要 full (generation 2) garbage collection 来清理。与 generation 0 和 generation 1 不同,generation 2 需要临时暂挂应用程序。故而频繁分配和取消分配大型对象会导致性能耗损。

建议 :

  • 要 考虑缓存频繁使用的大对象。缓存大对象可防止昂贵的分配开销。

  • 要使用 ArrayPool<T> 作为池化缓冲区以保存大型数组。

  • 不要 在代码热点路径 上分配许多短生命周期的大对象。

可以通过查看 PerfView 中的垃圾回收 (GC) 统计信息来诊断并检查内存问题,其中包括:

  • 垃圾回收挂起时间。

  • 垃圾回收中耗用的处理器时间百分比。

  • 有多少垃圾回收发生在 generation 0, 1, 和 2.

有关更多信息,请参阅 垃圾回收和性能。

优化数据操作和 I/O

与数据存储器和其他远程服务的交互通常是 ASP.NET Core 应用程序最慢的部分。高效读取和写入数据对于良好的性能至关重要。

建议 :

  • 要 以异步方式调用所有数据访问 API 。

  • 不要 读取不需要的数据。编写查询时,仅返回当前 HTTP 请求所必需的数据。

  • 要 考虑缓存从数据库或远程服务检索的频繁访问的数据 (如果稍微过时的数据是可接受的话) 。根据具体的场景,可以使用 MemoryCache 或 DistributedCache。有关更多信息,请参阅 https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-3.1.

  • 要 尽量减少网络往返。能够单次调用完成就不应该多次调用来读取所需数据。

  • 要 在 Entity Framework Core 访问数据以用作只读情况时, 使用 no-tracking 方式查询。EF Core 可以更高效地返回 no-tracking 查询的结果。

  • 要 使用过滤器和聚集 LINQ 查询 (例如, .Where, .Select 或 .Sum 语句) ,以便数据库执行过滤提高性能 。

  • 要 考虑 EF Core 可能在客户端解析一些查询运算符,这可能导致查询执行效率低下。有关更多信息,请参阅 客户端计算相关的性能问题。

  • 不要 在集合上使用映射查询,这会导致执行 “N + 1” SQL 查询。有关更多信息,请参阅 优化子查询。

请参阅 EF 高性能专题 以了解可能提高应用性能的方法:

  • DbContext 池

  • 显式编译的查询

在代码提交之前,我们建议评估上述高性能方法的影响。编译查询的额外复杂性可能无法一定确保性能提高。

可以通过使用 Application Insights 或使用分析工具查看访问数据所花费的时间来检测查询问题。大多数数据库还提供有关频繁执行的查询的统计信息,这也可以作为重要参考。

通过 HttpClientFactory 建立 HTTP 连接池

虽然 HttpClient 实现了 IDisposable 接口,但它其实被设计为可以重复使用单个实例。关闭 HttpClient 实例会使套接字在短时间内以 TIME_WAIT 状态打开。如果经常创建和释放 HttpClient 对象,那么应用程序可能会耗尽可用套接字。在 ASP.NET Core 2.1 中,引入了 HttpClientFactory 作为解决这个问题的办法。它以池化 HTTP 连接的方式从而优化性能和可靠性。

建议 :

  • 不要 直接创建和释放 HttpClient 实例。

  • 要 使用 HttpClientFactory 来获取 HttpClient 实例。有关更多信息,请参阅 使用 HttpClientFactory 以实现弹性 HTTP 请求。

确保公共代码路径快若鹰隼

如果你想要所有的代码都保持高速, 高频调用的代码路径就是优化的最关键路径。优化措施包括:

  • 考虑优化应用程序请求处理管道中的 Middleware ,尤其是在管道中排在更前面运行的 Middleware 。这些组件对性能有很大影响。

  • 考虑优化那些每个请求都要执行或每个请求多次执行的代码。例如,自定义日志,身份认证与授权或 transient 服务的创建等等。

建议 :

  • 不要 使用自定义 middleware 运行长时任务 。

  • 要 使用性能分析工具 ( 如 Visual Studio Diagnostic Tools 或 PerfView) 来定位 代码热点路径。

在 HTTP 请求之外运行长时任务

对 ASP.NET Core 应用程序的大多数请求可以由调用服务的 controller 或页面模型处理,并返回 HTTP 响应。对于涉及长时间运行的任务的某些请求,最好使整个请求 - 响应进程异步。

建议 :

  • 不要把等待长时间运行的任务完成,作为普通 HTTP 请求处理的一部分。

  • 要 考虑使用 后台服务 或 Azure Function 处理长时间运行的任务。在应用外执行任务特别有利于 CPU 密集型任务的性能。

  • 要 使用实时通信,如 SignalR,以异步方式与客户端通信。

缩小客户端资源

复杂的 ASP.NET Core 应用程序经常包含很有前端文件例如 JavaScript, CSS 或图片文件。可以通过以下方法优化初始请求的性能:

  • 打包,将多个文件合并为一个文件。

  • 压缩,通过除去空格和注释来缩小文件大小。

建议 :

  • 要 使用 ASP.NET Core 的 内置支持 用于打包和压缩客户端资源文件的组件。

  • 要 考虑其他第三方工具,如 Webpack,用于复杂客户资产管理。

压缩 Http 响应

减少响应的大小通常会显着提高应用程序的响应性。而减小内容大小的一种方法是压缩应用程序的响应。有关更多信息,请参阅 响应压缩。

使用最新的 ASP.NET Core 发行版

ASP.NET Core 的每个新发行版都包含性能改进。.NET Core 和 ASP.NET Core 中的优化意味着较新的版本通常优于较旧版本。例如, .NET Core 2.1 添加了对预编译的正则表达式的支持,并从使用 Span<T> 改进性能。ASP.NET Core 2.2 添加了对 HTTP/2 的支持。 ASP.NET Core 3.0 增加了许多改进 ,以减少内存使用量并提高吞吐量。如果性能是优先考虑的事情,那么请升级到 ASP.NET Core 的当前版本。

最小化异常

异常应该尽可能少。 相对于正常代码流程来说,抛出和捕获异常是缓慢的。 因此,不应使用异常来控制正常程序流。

建议 :

  • 不要 使用抛出或捕获异常作为正常程序流的手段,特别是在 代码热点路径 中。

  • 要 在应用程序中包含用于检测和处理导致异常的逻辑。

  • 要 对意外的执行情况抛出或捕获异常。

应用程序诊断工具 (如 Application Insights) 可以帮助识别应用程序中可能影响性能的常见异常。

性能和可靠性

下文将提供常见性能提示和已知可靠性问题的解决方案。

避免在 HttpRequest/HttpResponse body 上同步读取或写入

ASP.NET Core 中的所有 I/O 都是异步的。服务器实现了 Stream 接口,它同时具有同步和异步的方法重载。应该首选异步方式以避免阻塞线程池线程。阻塞线程会导致线程池饥饿。

不要使用如下操作: https://docs.microsoft.com/en-us/dotnet/api/System.IO.StreamReader.ReadToEnd。它会阻止当前线程等待结果。这是 sync over async 的示例。

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

在上述代码中, Get 采用同步的方式将整个 HTTP 请求主体读取到内存中。如果客户端上载数据很慢,那么应用程序就会出现看似异步实际同步的操作。应用程序看似异步实际同步,因为 Kestrel 不 支持同步读取。

应该采用如下操作: https://docs.microsoft.com/en-us/dotnet/api/System.IO.StreamReader.ReadToEndAsync ,在读取时不阻塞线程。

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

上述代码异步将整个 HTTP request body 读取到内存中。

[!WARNING] 如果请求很大,那么将整个 HTTP request body 读取到内存中可能会导致内存不足 (OOM) 。OOM 可导致应用奔溃。有关更多信息,请参阅 避免将大型请求主体或响应主体读取到内存中。

应该采用如下操作: 使用不缓冲的方式完成 request body 操作:

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

上述代码采用异步方式将 request body 序列化为 C# 对象。

优先选用 Request.Form 的 ReadFormAsync

应该使用 HttpContext.Request.ReadFormAsync 而不是 HttpContext.Request.Form。 HttpContext.Request.Form 只能在以下场景用安全使用。

  • 该表单已被 ReadFormAsync 调用,并且

  • 数据已经被从 HttpContext.Request.Form 读取并缓存

不要使用如下操作: 例如以下方式使用 HttpContext.Request.Form。 HttpContext.Request.Form 使用了 sync over async ,这将导致线程饥饿.

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

应该使用如下操作: 使用 HttpContext.Request.ReadFormAsync 异步读取表单正文。

public class GoodReadController : Controller
{[HttpPost("/form-body")]public async Task<IActionResult> Post(){var form = await HttpContext.Request.ReadFormAsync();Process(form["id"], form["name"]);return Accepted();}

避免将大型 request body 或 response body 读取到内存中

在 .NET 中,大于 85 KB 的对象会被分配在大对象堆 (LOH )。大型对象的开销较大,包含两方面:

  • 分配大对象内存时需要对被分配的内存进行清空,这个操作成本较高。CLR 会保证清空所有新分配的对象的内存。(将内存全部设置为 0)

  • LOH 只会在内存剩余不足时回收。LOH 需要在 full garbage collection 或者 Gen2 collection 进行回收。

此 博文 很好描述了该问题:

当分配大对象时,它会被标记为 Gen 2 对象。而不像是 Gen 0 那样的小对象。这样的后果是,如果你在使用 LOH 时耗尽内存, GC 会清除整个托管堆,而不仅仅是 LOH 部分。因此,它将清理 Gen 0, Gen 1 and Gen 2 (包括 LOH) 。这称为 full garbage collection,是最耗时的垃圾回收。对于很多应用,这是可以接受的。但绝对不适用于高性能 Web 服务器,因为高性能 Web 服务器需要更多的内存用于处理常规 Web 请求 ( 从套接字读取,解压缩,解码 JSON 等等 )。

天真地将一个大型 request 或者 response body 存储到单个 byte[] 或 string 中:

  • 这可能导致 LOH 的剩余空间快速耗尽。

  • 因此产生的 full GC 可能会导致应用程序的性能问题。

使用同步 API 处理数据

例如使用仅支持同步读取和写入的序列化器 / 反序列化器时 ( 例如, JSON.NET):

  • 将数据异步缓冲到内存中,然后将其传递到序列化器 / 反序列化器。

[!WARNING] 如果请求较大,那么可能导致内存不足 (OOM) 。OOM 可导致应用奔溃。有关更多信息,请参阅 避免将大型请求主体或响应主体读取到内存。

ASP.NET Core 3.0 默认情况下使用 https://docs.microsoft.com/en-us/dotnet/api/system.text.json 进行 JSON 序列化,这将带来如下好处。 https://docs.microsoft.com/en-us/dotnet/api/system.text.json:

  • 异步读取和写入 JSON 。

  • 针对 UTF-8 文本进行了优化。

  • 通常比 Newtonsoft.Json 更高的性能。

不要将 IHttpContextAccessor.HttpContext 存储在字段中

IHttpContextAccessor.HttpContext 返回当前请求线程中的 HttpContextIHttpContextAccessor.HttpContext** 不应该 ** 被存储在一个字段或变量中。

不要使用如下操作: 例如将 HttpContext 存储在字段中,然后在后续使用该字段。

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

以上代码在构造函数中经常得到 Null 或不正确的 HttpContext

应该采用如下操作:

  • 在字段中保存 https://docs.microsoft.com/en-us/aspnet/core/Microsoft.AspNetCore.Http.IHttpContextAccessor?view=aspnetcore-3.1。

  • 在恰当的时机获取并使用 HttpContext ,并检查是否为 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");}}
}

不要尝试在多线程下使用 HttpContext

HttpContext 不是 线程安全的。从多个线程并行访问 HttpContext 可能会导致不符预期的行为,例如线程挂起,崩溃和数据损坏。

不要使用如下操作: 以下示例将发出三个并行请求,并在 HTTP 请求之前和之后记录传入的请求路径。请求路径将被多个线程 (可能并行) 访问。

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

应该这样操作: 以下示例在发出三个并行请求之前,从传入请求复制下文需要使用的数据。

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

请求处理完成后不要使用 HttpContext

HttpContext 只有在 ASP.NET Core 管道处理活跃的 HTTP 请求时才可用。整个 ASP.NET Core 管道是由异步代理组成的调用链,用于处理每个请求。当 Task 从调用链完成并返回时,HttpContext 就会被回收。

不要进行如下操作: 以下示例使用 async void ,这将使得 HTTP 请求在第一个 await 时处理完成,进而就会导致:

  • 在 ASP.NET Core 应用程序中, 这是一个完全错误 的做法

  • 在 HTTP 请求完成后访问 HttpResponse

  • 进程崩溃。

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

应该进行如下操作: 以下示例将 Task 返回给框架,因此,在操作完成之前, HTTP 请求不会完成。

public class AsyncGoodTaskController : Controller
{[HttpGet("/async")]public async Task Get(){await Task.Delay(1000);await Response.WriteAsync("Hello World");}
}

不要在后台线程中使用 HttpContext

不要使用如下操作: 以下示例使用一个闭包从 Controller 属性读取 HttpContext。这是一种错误做法,因为这将导致:

  • 代码运行在 Http 请求作用域之外。

  • 尝试读取错误的 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();
}

应该采用如下操作:

  • 在请求处理阶段将后台线程需要的数据全部进行复制。

  • 不要使用 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();
}

后台任务最好采用托管服务进行操作。有关更多信息,请参阅 采用托管服务运行后台任务 。

不要在后台线程获取注入到 controller 中的服务

不要采用如下做法: 以下示例使用闭包从 controller 获取 DbContext 进行操作。这是一个错误的做法。这将导致代码云在请求的作用域之外。而 ContocoDbContext 是基于请求作用域的,因此这样将引发 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();
}

应该采用如下操作:

  • 注入 https://docs.microsoft.com/en-us/aspnet/core/Microsoft.Extensions.DependencyInjection.IServiceScopeFactory?view=aspnetcore-3.1 ,并且在后台线程中创建新的作用域。 IServiceScopeFactory 是一个单例对象,所以这样没有问题。

  • 在后台线程中创建新作用域注入依赖的服务。

  • 不要引用 controller 的所有内容

  • 不要从请求中读取 ContocoDbContext

[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactoryserviceScopeFactory)
{_ = Task.Run(async () =>{await Task.Delay(1000);using (var scope = serviceScopeFactory.CreateScope()){var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();context.Contoso.Add(new Contoso());await context.SaveChangesAsync();}});return Accepted();
}

以下高亮的的代码说明:

  • 为后台操作创建新的作用域,并且从中获取需要的服务。

  • 在正确的作用域中使用 ContocoDbContext,即只能在请求作用域中使用该对象。

[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactoryserviceScopeFactory)
{_ = Task.Run(async () =>{await Task.Delay(1000);using (var scope = serviceScopeFactory.CreateScope()){var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();context.Contoso.Add(new Contoso());await context.SaveChangesAsync();}});return Accepted();
}

不要在响应正文已经开始发送时尝试修改 status code 或者 header

ASP.NET Core 不会缓冲 HTTP 响应正文。当正文一旦开始发送:

  • Header 就会与正文的数据包一起发送到客户端。

  • 此时就无法修改 header 了。

不要使用如下操作: 以下代码尝试在响应启动后添加响应头:

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

在上述的代码中,如果 next() 已经开始写入响应,则 context.Response.Headers["test"] = "test value"; 将会抛出异常。

应该采用如下操作: 以下示例检查 HTTP 响应在修改 Header 之前是否已启动。

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

应该采用如下操作: 以下示例使用 HttpResponse.OnStarting 来设置 Header,这样便可以在响应启动时将 Header 一次性写入到客户端。

通过这种方式,响应头将在响应开始时调用已注册的回调进行一次性写入。如此这般便可以:

  • 在恰当的时候进行响应头的修改或者覆盖。

  • 不需要了解管道中的下一个 middleware 的行为。

app.Use(async (context, next) =>
{context.Response.OnStarting(() =>{context.Response.Headers["someheader"] = "somevalue";return Task.CompletedTask;});await next();
});

如果已开始写入响应主体,则请不要调用 next ()

仅当后续组件能够处理响应或时才调用它们,因此如果当前已经开始写入响应主体,后续操作就已经不再需要,并有可能引发异常情况。

托管于 IIS 应该使用 In-process 模式

使用 in-process 模式托管, ASP.NET Core 应用程序将与 IIS 工作进程在同一进程中运行。In-process 模式拥有比 out-of-process 更加优秀的性能表现,因为这样不需要将请求通过回环网络适配器进行代理中转。回环网络适配器是将本机发送的网络流量重新转回本机的的网络适配器。IIS 进程管理由 Windows Process Activation Service (WAS) 来完成。

在 ASP.NET Core 3.0 和更高版本中的默认将采用 in-process 模式进行托管。

有关更多信息,请参阅 在 Windows 上使用 IIS 托管 ASP.NET Core


Newbe.Translations

您所阅读的当前文章源自于 Newbe.Translations 项目参与的翻译贡献,您可以通过右侧链接一同参与该项目:https://www.newbe.pro/Newbe.Translations/Newbe.Translations/。

翻译内容具有一定的时效性,不会随着原文内容实时更新,如果内容存在一定过时,您也可以联系我们。

  • 本文作者: newbe36524

  • 本文链接: https://www.newbe.pro/Newbe.Translations/001-ASP.NET-Core-Performance-Best-Practices/

  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307779.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[算法与数据结构] 谈谈线性查找法~

[算法与数据结构] 一文详解线性查找法~&#x1f4c5;前言一、&#x1f4dd;算法基础知识1、什么是算法2、算法的五大特性二、&#x1f4c8;线性查找法1、举例阐述2、实现线性查找法3、使用泛型4、升级改造5、使用自定义类6、循环不变量三、&#x1f4ca;算法复杂度1、简单的复杂…

送福利 | 送书5本《ASP.NET Core项目开发实战入门》带你走进ASP.NET Core开发

《ASP.NET Core项目开发实战入门》从基础到实际项目开发部署带你走进ASP.NET Core开发。ASP.NET Core项目开发实战入门是基于ASP.NET Core 3.1 所写&#xff0c;后续ASP.NET Core 5也会对应更新。简介本书共14章&#xff0c;深入浅出地介绍了ASP.NET Core基础及项目开发方面的知…

海有舟可渡、山有路可循‍‍ —— 大学四年圆满落幕

⛺️前言 仅以此篇文章记录我的大学四年&#xff01; 回忆大学四年&#xff0c;有惊喜、有快乐、有崩溃、有欢呼&#xff1b; 有过无数个为了赶策划、赶ddl的熬夜&#xff0c;更有凌晨2点做志愿活动、凌晨5点赶项目的通宵达旦。 梳理下大学四年的大事件&#xff1a; 大一 …

程序开发天团必备单品,稳定输出还加持千元商务礼限时送!

有这么一个门派他们用强大的思维逻辑频出“大招”用抗造的硬核体能昼夜练习一水儿的格子衫下&#xff0c;个个骨骼精奇深居简出&#xff0c;却默默改变着世界格局他们都使用同一件武器拼的就是个配置高低这与能否称霸武林有着直接关系看看各位高手怎么说“大内”高手 武功唯快不…

了解微前端,深入前端架构的前世今生

前端架构的前世今生&#x1f6f5;前言一、&#x1f6f4;前端架构的前世今生1、架构是如何产生的&#xff1f;2、MVC架构3、前后端分离架构4、Nodejs5、单页面架构&#xff08;1&#xff09;现有单页面架构&#xff08;2&#xff09;单页面架构的优势&#xff08;3&#xff09;单…

leetcode37. 解数独

一:论语 简而言之 就是要一视同仁 对待遇见所有的人要一个态度 二&#xff1a;题目 三:上码 class Solution { public:bool backstacking(vector<vector<char> >& board) {for(int i 0; i < board.size(); i) {for(int j 0; j < board[0].size(); …

跟我一起学.NetCore之.NetCore概述

随着.NetCore版本发布变更&#xff0c;在最近一年左右开始接触到.NetCore&#xff0c;之前只是传闻&#xff0c;并没有动手实操&#xff1b;.NetCore逐渐成熟&#xff0c;大大小小的公司也开始进行使用&#xff0c;感觉再不学习就落后了&#xff0c;于是乎搜索各种资料开始学习…

线程与线程池(一条龙详解)

一:前言 一个问题引出的学习笔记 并发类库提供的线程池实现有哪些? 其实Executors已经为我们封装好了 4 种常见的功能线程池&#xff0c;如下&#xff1a; 定长线程池&#xff08;FixedThreadPool&#xff09;定时线程池&#xff08;ScheduledThreadPool &#xff09;可缓存…

项目升级,无缝对接 .NET 5

开启.NET5时代2020-09-14从NetCore1.1开始学起&#xff0c;然后又从2.0开始讲知识&#xff0c;再到将所有的在线项目升级并长期维护到3.1&#xff0c;转眼已经三年了&#xff0c;一直紧跟着微软的节奏有条不紊的往前走&#xff0c;我相信&#xff0c;只要是从18年末或者19年初跟…

小团队前端部署演化之路

前言 前端部署相对来说其实是一件非常容易的事情&#xff0c;无论是最原始的html页面&#xff0c;还是现在热门的三大框架&#xff0c;最后交付部署的时候&#xff0c;始终会是一些静态文件。虽然简单&#xff0c;但是对于不同的团队来说&#xff0c;都会在不同阶段有最适合他们…

GitHub 全域数字年报:携手推动开源世界的超级协作

2020年1月24日&#xff0c;Wuhan2020开源项目正式发起&#xff0c;在疫情期间累积吸引到了约3000余位技术志愿者以及近1000余位非技术志愿者在线上开展志愿行动与参与。Wuhan2020在成立后的约3个月时间内&#xff0c;通过开源协作的方式在互联网上开展志愿者支持与工作协同&…

leetcode53. 最大子数组和(暴力+贪心)

一:论语 追求利益的同时 我们需要控制度 就好比鹅厂的王者荣耀 赚的盆满钵满 坑坏了多少青少年 但是鹅厂早已经开始控制度了 二:题目 三:上码&#xff08;暴力贪心&#xff09; 1:暴力 class Solution { public:int maxSubArray(vector<int>& nums) {/**暴力解法…

Newbe.Claptrap-一套以“事件溯源”和“Actor模式”作为基本理论的服务端开发框架...

本文是关于 Newbe.Claptrap 项目主体内容的介绍&#xff0c;读者可以通过这篇文章&#xff0c;大体了解项目内容。轮子源于需求随着互联网应用的蓬勃发展&#xff0c;相关的技术理论和实现手段也在被不断创造出来。诸如 “云原生架构”、“微服务架构”、“DevOps” 等一系列关…

NCF框架揭秘直播来了!红包、抽奖、还有神秘嘉宾…(内含彩蛋)

盛派周三技术分享会直播开讲又来啦~为了更好地赋能开发者&#xff0c;盛派已将系统框架 SCF&#xff08;SenparcCoreFramework&#xff09;全部开源&#xff0c;收到了社区非常多的关注&#xff0c;现在 SCF 已正式更名为NCF&#xff08;NeuCharFramework&#xff09;&#xff…

leetcode45. 跳跃游戏 II

一:论语 己所欲 也要勿施于人 &#xff0c;每个人的经历和阅历都是不同的 你凭啥说你认为的很开心的事情 去要求别人呢 二:题目 三:上码 class Solution { public:int jump(vector<int>& nums) {/**思路:1.这里的难点就在于 我们需要判断下一步的的最远跳跃距离…

【翻译】.NET 5 RC1发布

9月14日&#xff0c;.NET5发布了(Release Candidate)RC1版本&#xff0c;RC的意思是指我们可以进行使用&#xff0c;并且RC版本得到了支持&#xff0c;该版本是接近.NET5的版本&#xff0c;也是11月正式版本之前两个RC版本中的其中一个。目前&#xff0c;开发团队正在寻找在.NE…

leetcode1005. K 次取反后最大化的数组和

一:论语 这个用在自己身上感觉值得反省&#xff0c;很多道理我都能明白 也能讲给别人听 但是很多时候 自己往往做的不好 而且还很容易 自我感动 最近真的很讨厌自己这样 不要自我感动 要正向积累 多去做 多去做 这只是个开始 然后慢慢的长进 再者就是坚持 二:题目 三:上码 …

.NET Core全Linux开发体验分享

“ 2016年.NET Core首个正式版本问世&#xff0c;如今已发布到了.NET Core3.1&#xff0c;再有2个月.NET5也将如约而至&#xff0c;跨平台开发已经快5年&#xff0c;然而很多人却还只是在Windows上用Visual Studio SQL Server去做.NET Core跨平台开发&#xff0c;欠缺对Linux的…

使用Microsoft Word2016无法正常对Latex文本转换的踩坑和解决方法

相信很多人都遇到像我一样的问题。word2016中&#xff0c;有latex的按钮&#xff0c;按ALT就可以开始写公式&#xff0c;复制粘贴latex公式之后&#xff0c;怎么就转换不了呢&#xff1f;就是如图这样的&#xff0c;左上角转换按钮为灰色。 上网找呀找&#xff0c;找了很多资料…

leetcode134. 加油站

一:论语 二&#xff1a;题目 三&#xff1a;上码(暴力解法超时 但方法二还是可以的) // class Solution { // public: // int canCompleteCircuit(vector<int>& gas, vector<int>& cost) { // /** // 思路:1.暴力解法,我们遍历所…