本篇內(nèi)容主要講解“ASP.NET Core性能優(yōu)化的方法是什么”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“ASP.NET Core性能優(yōu)化的方法是什么”吧!
我們提供的服務(wù)有:網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè)、微信公眾號(hào)開(kāi)發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、懷化ssl等。為上千余家企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的懷化網(wǎng)站制作公司
在本文檔中, 代碼熱點(diǎn)路徑 定義為頻繁調(diào)用的代碼路徑以及執(zhí)行時(shí)間的大部分時(shí)間。 代碼熱點(diǎn)路徑通常限制應(yīng)用程序的擴(kuò)展和性能,并在本文檔的多個(gè)部分中進(jìn)行討論。
ASP.NET Core 應(yīng)用程序應(yīng)設(shè)計(jì)為同時(shí)處理許多請(qǐng)求。 異步 API 可以使用一個(gè)小池線程通過(guò)非阻塞式調(diào)用來(lái)處理數(shù)以千計(jì)的并發(fā)請(qǐng)求。 線程可以處理另一個(gè)請(qǐng)求,而不是等待長(zhǎng)時(shí)間運(yùn)行的同步任務(wù)完成。
ASP.NET Core 應(yīng)用程序中的常見(jiàn)性能問(wèn)題通常是由于那些本可以異步調(diào)用但卻采用阻塞時(shí)調(diào)用而導(dǎo)致的。 同步阻塞會(huì)調(diào)用導(dǎo)致 線程池饑餓 和響應(yīng)時(shí)間降級(jí)。
不要:
通過(guò)調(diào)用 Task.Wait 或 Task.Result 來(lái)阻止異步執(zhí)行。
在公共代碼路徑中加鎖。 ASP.NET Core 應(yīng)用程序應(yīng)設(shè)計(jì)為并行運(yùn)行代碼,如此才能使得性能最佳。
調(diào)用 Task.Run 并立即 await 。 ASP.NET Core 本身已經(jīng)是在線程池線程上運(yùn)行應(yīng)用程序代碼了,因此這樣調(diào)用 Task.Run 只會(huì)導(dǎo)致額外的不必要的線程池調(diào)度。 而且即使被調(diào)度的代碼會(huì)阻止線程, Task.Run 也并不能避免這種情況,這樣做沒(méi)有意義。
要:
確保 代碼熱點(diǎn)路徑 全部異步化。
如在進(jìn)行調(diào)用數(shù)據(jù)讀寫、I/O 處理和長(zhǎng)時(shí)間操作的 API 時(shí),存在可用的異步 API。那么務(wù)必選擇異步 API 。 但是,不要 使用 Task.Run 來(lái)包裝同步 API 使其異步化。
確保 controller/Razor Page actions 異步化。 整個(gè)調(diào)用堆棧是異步的,就可以利用 async/await 模式的性能優(yōu)勢(shì)。
使用性能分析程序 ( 例如 PerfView) 可用于查找頻繁添加到 線程池 的線程。 Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start
事件表示新線程被添加到線程池。
在 Action 中返回 IEumerable<T>
將會(huì)被序列化器中進(jìn)行同步迭代 。 結(jié)果是可能導(dǎo)致阻塞或者線程池饑餓。 想要要避免同步迭代集合,可以在返回迭代集合之前使用 ToListAsync
使其異步化。
從 ASP.NET Core 3.0 開(kāi)始, IAsyncEnumerable<T>
可以用作為 IEumerable<T>
的替代方法,以異步方式進(jìn)行迭代。 有關(guān)更多信息,請(qǐng)參閱 Controller Action 的返回值類型。
.NET Core 垃圾收集器 在 ASP.NET Core 應(yīng)用程序中起到自動(dòng)管理內(nèi)存的分配和釋放的作用。 自動(dòng)垃圾回收通常意味著開(kāi)發(fā)者不需要擔(dān)心如何或何時(shí)釋放內(nèi)存。 但是,清除未引用的對(duì)象將會(huì)占用 CPU 時(shí)間,因此開(kāi)發(fā)者應(yīng)最小化 代碼熱點(diǎn)路徑 中的分配的對(duì)象。 垃圾回收在大對(duì)象上代價(jià)特大 (> 85 K 字節(jié)) 。 大對(duì)象存儲(chǔ)在 large object heap 上,需要 full (generation 2) garbage collection 來(lái)清理。 與 generation 0 和 generation 1 不同,generation 2 需要臨時(shí)暫掛應(yīng)用程序。 故而頻繁分配和取消分配大型對(duì)象會(huì)導(dǎo)致性能耗損。
建議 :
要 考慮緩存頻繁使用的大對(duì)象。 緩存大對(duì)象可防止昂貴的分配開(kāi)銷。
要使用 ArrayPool<T> 作為池化緩沖區(qū)以保存大型數(shù)組。
不要 在代碼熱點(diǎn)路徑 上分配許多短生命周期的大對(duì)象。
可以通過(guò)查看 PerfView 中的垃圾回收 (GC) 統(tǒng)計(jì)信息來(lái)診斷并檢查內(nèi)存問(wèn)題,其中包括:
垃圾回收掛起時(shí)間。
垃圾回收中耗用的處理器時(shí)間百分比。
有多少垃圾回收發(fā)生在 generation 0, 1, 和 2.
有關(guān)更多信息,請(qǐng)參閱 垃圾回收和性能。
與數(shù)據(jù)存儲(chǔ)器和其他遠(yuǎn)程服務(wù)的交互通常是 ASP.NET Core 應(yīng)用程序最慢的部分。 高效讀取和寫入數(shù)據(jù)對(duì)于良好的性能至關(guān)重要。
建議 :
要 以異步方式調(diào)用所有數(shù)據(jù)訪問(wèn) API 。
不要 讀取不需要的數(shù)據(jù)。 編寫查詢時(shí),僅返回當(dāng)前 HTTP 請(qǐng)求所必需的數(shù)據(jù)。
要 考慮緩存從數(shù)據(jù)庫(kù)或遠(yuǎn)程服務(wù)檢索的頻繁訪問(wèn)的數(shù)據(jù) (如果稍微過(guò)時(shí)的數(shù)據(jù)是可接受的話) 。 根據(jù)具體的場(chǎng)景,可以使用 MemoryCache 或 DistributedCache。 有關(guān)更多信息,請(qǐng)參閱 https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-3.1.
要 盡量減少網(wǎng)絡(luò)往返。 能夠單次調(diào)用完成就不應(yīng)該多次調(diào)用來(lái)讀取所需數(shù)據(jù)。
要 在 Entity Framework Core 訪問(wèn)數(shù)據(jù)以用作只讀情況時(shí), 使用 no-tracking 方式查詢。 EF Core 可以更高效地返回 no-tracking 查詢的結(jié)果。
要 使用過(guò)濾器和聚集 LINQ 查詢 (例如, .Where
, .Select
或 .Sum
語(yǔ)句) ,以便數(shù)據(jù)庫(kù)執(zhí)行過(guò)濾提高性能 。
要 考慮 EF Core 可能在客戶端解析一些查詢運(yùn)算符,這可能導(dǎo)致查詢執(zhí)行效率低下。 有關(guān)更多信息,請(qǐng)參閱 客戶端計(jì)算相關(guān)的性能問(wèn)題。
不要 在集合上使用映射查詢,這會(huì)導(dǎo)致執(zhí)行 “N + 1” SQL 查詢。 有關(guān)更多信息,請(qǐng)參閱 優(yōu)化子查詢。
請(qǐng)參閱 EF 高性能專題 以了解可能提高應(yīng)用性能的方法:
DbContext 池
顯式編譯的查詢
在代碼提交之前,我們建議評(píng)估上述高性能方法的影響。 編譯查詢的額外復(fù)雜性可能無(wú)法一定確保性能提高。
可以通過(guò)使用 Application Insights 或使用分析工具查看訪問(wèn)數(shù)據(jù)所花費(fèi)的時(shí)間來(lái)檢測(cè)查詢問(wèn)題。 大多數(shù)數(shù)據(jù)庫(kù)還提供有關(guān)頻繁執(zhí)行的查詢的統(tǒng)計(jì)信息,這也可以作為重要參考。
雖然 HttpClient 實(shí)現(xiàn)了 IDisposable
接口,但它其實(shí)被設(shè)計(jì)為可以重復(fù)使用單個(gè)實(shí)例。 關(guān)閉 HttpClient
實(shí)例會(huì)使套接字在短時(shí)間內(nèi)以 TIME_WAIT
狀態(tài)打開(kāi)。 如果經(jīng)常創(chuàng)建和釋放 HttpClient
對(duì)象,那么應(yīng)用程序可能會(huì)耗盡可用套接字。 在 ASP.NET Core 2.1 中,引入了 HttpClientFactory 作為解決這個(gè)問(wèn)題的辦法。 它以池化 HTTP 連接的方式從而優(yōu)化性能和可靠性。
建議 :
不要 直接創(chuàng)建和釋放 HttpClient
實(shí)例。
要 使用 HttpClientFactory 來(lái)獲取 HttpClient
實(shí)例。 有關(guān)更多信息,請(qǐng)參閱 使用 HttpClientFactory 以實(shí)現(xiàn)彈性 HTTP 請(qǐng)求。
如果你想要所有的代碼都保持高速, 高頻調(diào)用的代碼路徑就是優(yōu)化的最關(guān)鍵路徑。 優(yōu)化措施包括:
考慮優(yōu)化應(yīng)用程序請(qǐng)求處理管道中的 Middleware ,尤其是在管道中排在更前面運(yùn)行的 Middleware 。 這些組件對(duì)性能有很大影響。
考慮優(yōu)化那些每個(gè)請(qǐng)求都要執(zhí)行或每個(gè)請(qǐng)求多次執(zhí)行的代碼。 例如,自定義日志,身份認(rèn)證與授權(quán)或 transient 服務(wù)的創(chuàng)建等等。
建議 :
不要 使用自定義 middleware 運(yùn)行長(zhǎng)時(shí)任務(wù) 。
要 使用性能分析工具 ( 如 Visual Studio Diagnostic Tools 或 PerfView) 來(lái)定位 代碼熱點(diǎn)路徑。
對(duì) ASP.NET Core 應(yīng)用程序的大多數(shù)請(qǐng)求可以由調(diào)用服務(wù)的 controller 或頁(yè)面模型處理,并返回 HTTP 響應(yīng)。 對(duì)于涉及長(zhǎng)時(shí)間運(yùn)行的任務(wù)的某些請(qǐng)求,最好使整個(gè)請(qǐng)求 - 響應(yīng)進(jìn)程異步。
建議 :
不要把等待長(zhǎng)時(shí)間運(yùn)行的任務(wù)完成,作為普通 HTTP 請(qǐng)求處理的一部分。
要 考慮使用 后臺(tái)服務(wù) 或 Azure Function 處理長(zhǎng)時(shí)間運(yùn)行的任務(wù)。 在應(yīng)用外執(zhí)行任務(wù)特別有利于 CPU 密集型任務(wù)的性能。
要 使用實(shí)時(shí)通信,如 SignalR,以異步方式與客戶端通信。
復(fù)雜的 ASP.NET Core 應(yīng)用程序經(jīng)常包含很有前端文件例如 JavaScript, CSS 或圖片文件。 可以通過(guò)以下方法優(yōu)化初始請(qǐng)求的性能:
打包,將多個(gè)文件合并為一個(gè)文件。
壓縮,通過(guò)除去空格和注釋來(lái)縮小文件大小。
建議 :
要 使用 ASP.NET Core 的 內(nèi)置支持 用于打包和壓縮客戶端資源文件的組件。
要 考慮其他第三方工具,如 Webpack,用于復(fù)雜客戶資產(chǎn)管理。
減少響應(yīng)的大小通常會(huì)顯著提高應(yīng)用程序的響應(yīng)性。 而減小內(nèi)容大小的一種方法是壓縮應(yīng)用程序的響應(yīng)。 有關(guān)更多信息,請(qǐng)參閱 響應(yīng)壓縮。
ASP.NET Core 的每個(gè)新發(fā)行版都包含性能改進(jìn)。 .NET Core 和 ASP.NET Core 中的優(yōu)化意味著較新的版本通常優(yōu)于較舊版本。 例如, .NET Core 2.1 添加了對(duì)預(yù)編譯的正則表達(dá)式的支持,并從使用 Span<T> 改進(jìn)性能。 ASP.NET Core 2.2 添加了對(duì) HTTP/2 的支持。 ASP.NET Core 3.0 增加了許多改進(jìn) ,以減少內(nèi)存使用量并提高吞吐量。 如果性能是優(yōu)先考慮的事情,那么請(qǐng)升級(jí)到 ASP.NET Core 的當(dāng)前版本。
異常應(yīng)該竟可能少。 相對(duì)于正常代碼流程來(lái)說(shuō),拋出和捕獲異常是緩慢的。 因此,不應(yīng)使用異常來(lái)控制正常程序流。
建議 :
不要 使用拋出或捕獲異常作為正常程序流的手段,特別是在 代碼熱點(diǎn)路徑 中。
要 在應(yīng)用程序中包含用于檢測(cè)和處理導(dǎo)致異常的邏輯。
要 對(duì)意外的執(zhí)行情況拋出或捕獲異常。
應(yīng)用程序診斷工具 (如 Application Insights) 可以幫助識(shí)別應(yīng)用程序中可能影響性能的常見(jiàn)異常。
下文將提供常見(jiàn)性能提示和已知可靠性問(wèn)題的解決方案。
ASP.NET Core 中的所有 I/O 都是異步的。 服務(wù)器實(shí)現(xiàn)了 Stream
接口,它同時(shí)具有同步和異步的方法重載。 應(yīng)該首選異步方式以避免阻塞線程池線程。 阻塞線程會(huì)導(dǎo)致線程池饑餓。
不要使用如下操作: https://docs.microsoft.com/en-us/dotnet/api/System.IO.StreamReader.ReadToEnd。 它會(huì)阻止當(dāng)前線程等待結(jié)果。 這是 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
采用同步的方式將整個(gè) HTTP 請(qǐng)求主體讀取到內(nèi)存中。 如果客戶端上載數(shù)據(jù)很慢,那么應(yīng)用程序就會(huì)出現(xiàn)看似異步實(shí)際同步的操作。 應(yīng)用程序看似異步實(shí)際同步,因?yàn)?Kestrel 不 支持同步讀取。
應(yīng)該采用如下操作: https://docs.microsoft.com/en-us/dotnet/api/System.IO.StreamReader.ReadToEndAsync ,在讀取時(shí)不阻塞線程。
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); }} |
上述代碼異步將整個(gè) HTTP request body 讀取到內(nèi)存中。
[!WARNING] 如果請(qǐng)求很大,那么將整個(gè) HTTP request body 讀取到內(nèi)存中可能會(huì)導(dǎo)致內(nèi)存不足 (OOM) 。 OOM 可導(dǎo)致應(yīng)用奔潰。 有關(guān)更多信息,請(qǐng)參閱 避免將大型請(qǐng)求主體或響應(yīng)主體讀取到內(nèi)存中。
應(yīng)該采用如下操作: 使用不緩沖的方式完成 request body 操作:
public class GoodStreamReaderController : Controller{ [HttpGet("/contoso")] public async Task<ActionResult<ContosoData>> Get() { return await JsonSerializer.DeserializeAsync<ContosoData>(Request.Body); }} |
上述代碼采用異步方式將 request body 序列化為 C# 對(duì)象。
應(yīng)該使用 HttpContext.Request.ReadFormAsync
而不是 HttpContext.Request.Form
。 HttpContext.Request.Form
只能在以下場(chǎng)景用安全使用。
該表單已被 ReadFormAsync
調(diào)用,并且
數(shù)據(jù)已經(jīng)被從 HttpContext.Request.Form
讀取并緩存
不要使用如下操作: 例如以下方式使用 HttpContext.Request.Form
。 HttpContext.Request.Form
使用了 sync over async ,這將導(dǎo)致線程饑餓.
public class BadReadController : Controller{ [HttpPost("/form-body")] public IActionResult Post() { var form = HttpContext.Request.Form; Process(form["id"], form["name"]); return Accepted(); } |
應(yīng)該使用如下操作: 使用 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(); } |
在 .NET 中,大于 85 KB 的對(duì)象會(huì)被分配在大對(duì)象堆 (LOH )。 大型對(duì)象的開(kāi)銷較大,包含兩方面:
分配大對(duì)象內(nèi)存時(shí)需要對(duì)被分配的內(nèi)存進(jìn)行清空,這個(gè)操作成本較高。 CLR 會(huì)保證清空所有新分配的對(duì)象的內(nèi)存。(將內(nèi)存全部設(shè)置為 0)
LOH 只會(huì)在內(nèi)存剩余不足時(shí)回收。 LOH 需要在 full garbage collection 或者 Gen2 collection 進(jìn)行回收。
此 博文 很好描述了該問(wèn)題:
當(dāng)分配大對(duì)象時(shí),它會(huì)被標(biāo)記為 Gen 2 對(duì)象。 而不像是 Gen 0 那樣的小對(duì)象。 這樣的后果是,如果你在使用 LOH 時(shí)耗盡內(nèi)存, GC 會(huì)清除整個(gè)托管堆,而不僅僅是 LOH 部分。 因此,它將清理 Gen 0, Gen 1 and Gen 2 (包括 LOH) 。 這稱為 full garbage collection,是最耗時(shí)的垃圾回收。 對(duì)于很多應(yīng)用,這是可以接受的。 但絕對(duì)不適用于高性能 Web 服務(wù)器,因?yàn)楦咝阅?Web 服務(wù)器需要更多的內(nèi)存用于處理常規(guī) Web 請(qǐng)求 ( 從套接字讀取,解壓縮,解碼 JSON 等等 )。
天真地將一個(gè)大型 request 或者 response body 存儲(chǔ)到單個(gè) byte[]
或 string
中:
這可能導(dǎo)致 LOH 的剩余空間快速耗盡。
因此產(chǎn)生的 full GC 可能會(huì)導(dǎo)致應(yīng)用程序的性能問(wèn)題。
例如使用僅支持同步讀取和寫入的序列化器 / 反序列化器時(shí) ( 例如, JSON.NET):
將數(shù)據(jù)異步緩沖到內(nèi)存中,然后將其傳遞到序列化器 / 反序列化器。
[!WARNING] 如果請(qǐng)求較大,那么可能導(dǎo)致內(nèi)存不足 (OOM) 。 OOM 可導(dǎo)致應(yīng)用奔潰。 有關(guān)更多信息,請(qǐng)參閱 避免將大型請(qǐng)求主體或響應(yīng)主體讀取到內(nèi)存。
ASP.NET Core 3.0 默認(rèn)情況下使用 https://docs.microsoft.com/en-us/dotnet/api/system.text.json 進(jìn)行 JSON 序列化,這將帶來(lái)如下好處。 https://docs.microsoft.com/en-us/dotnet/api/system.text.json:
異步讀取和寫入 JSON 。
針對(duì) UTF-8 文本進(jìn)行了優(yōu)化。
通常比 Newtonsoft.Json
更高的性能。
IHttpContextAccessor.HttpContext 返回當(dāng)前請(qǐng)求線程中的 HttpContext
. IHttpContextAccessor.HttpContext
** 不應(yīng)該 ** 被存儲(chǔ)在一個(gè)字段或變量中。
不要使用如下操作: 例如將 HttpContext
存儲(chǔ)在字段中,然后在后續(xù)使用該字段。
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"); } }} |
以上代碼在構(gòu)造函數(shù)中經(jīng)常得到 Null 或不正確的 HttpContext
。
應(yīng)該采用如下操作:
在字段中保存 https://docs.microsoft.com/en-us/aspnet/core/Microsoft.AspNetCore.Http.IHttpContextAccessor?view=aspnetcore-3.1。
在恰當(dāng)?shù)臅r(shí)機(jī)獲取并使用 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
不是 線程安全的。 從多個(gè)線程并行訪問(wèn) HttpContext
可能會(huì)導(dǎo)致不符預(yù)期的行為,例如線程掛起,崩潰和數(shù)據(jù)損壞。
不要使用如下操作: 以下示例將發(fā)出三個(gè)并行請(qǐng)求,并在 HTTP 請(qǐng)求之前和之后記錄傳入的請(qǐng)求路徑。 請(qǐng)求路徑將被多個(gè)線程 (可能并行) 訪問(wèn)。
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; } |
應(yīng)該這樣操作: 以下示例在發(fā)出三個(gè)并行請(qǐng)求之前,從傳入請(qǐng)求復(fù)制下文需要使用的數(shù)據(jù)。
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
只有在 ASP.NET Core 管道處理活躍的 HTTP 請(qǐng)求時(shí)才可用。 整個(gè) ASP.NET Core 管道是由異步代理組成的調(diào)用鏈,用于處理每個(gè)請(qǐng)求。 當(dāng) Task
從調(diào)用鏈完成并返回時(shí),HttpContext
就會(huì)被回收。
不要進(jìn)行如下操作: 以下示例使用 async void
,這將使得 HTTP 請(qǐng)求在第一個(gè) await
時(shí)處理完成,進(jìn)而就會(huì)導(dǎo)致:
在 ASP.NET Core 應(yīng)用程序中, 這是一個(gè)完全錯(cuò)誤 的做法
在 HTTP 請(qǐng)求完成后訪問(wèn) HttpResponse
。
進(jìn)程崩潰。
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"); }} |
應(yīng)該進(jìn)行如下操作: 以下示例將 Task
返回給框架,因此,在操作完成之前, HTTP 請(qǐng)求不會(huì)完成。
public class AsyncGoodTaskController : Controller{ [HttpGet("/async")] public async Task Get() { await Task.Delay(1000); await Response.WriteAsync("Hello World"); }} |
不要使用如下操作: 以下示例使用一個(gè)閉包從 Controller
屬性讀取 HttpContext
。 這是一種錯(cuò)誤做法,因?yàn)檫@將導(dǎo)致:
代碼運(yùn)行在 Http 請(qǐng)求作用域之外。
嘗試讀取錯(cuò)誤的 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();} |
應(yīng)該采用如下操作:
在請(qǐng)求處理階段將后臺(tái)線程需要的數(shù)據(jù)全部進(jìn)行復(fù)制。
不要使用 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();} |
后臺(tái)任務(wù)最好采用托管服務(wù)進(jìn)行操作。 有關(guān)更多信息,請(qǐng)參閱 采用托管服務(wù)運(yùn)行后臺(tái)任務(wù) 。
不要采用如下做法: 以下示例使用閉包從 controller
獲取 DbContext
進(jìn)行操作。 這是一個(gè)錯(cuò)誤的做法。 這將導(dǎo)致代碼云在請(qǐng)求的作用域之外。 而 ContocoDbContext
是基于請(qǐng)求作用域的,因此這樣將引發(fā) 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();} |
應(yīng)該采用如下操作:
注入 https://docs.microsoft.com/en-us/aspnet/core/Microsoft.Extensions.DependencyInjection.IServiceScopeFactory?view=aspnetcore-3.1 ,并且在后臺(tái)線程中創(chuàng)建新的作用域。 IServiceScopeFactory
是一個(gè)單例對(duì)象,所以這樣沒(méi)有問(wèn)題。
在后臺(tái)線程中創(chuàng)建新作用域注入依賴的服務(wù)。
不要引用 controller 的所有內(nèi)容
不要從請(qǐng)求中讀取 ContocoDbContext
。
[HttpGet("/fire-and-forget-3")]public IActionResult FireAndForget3([FromServices]IServiceScopeFactory serviceScopeFactory){ _ = 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();} |
以下高亮的的代碼說(shuō)明:
為后臺(tái)操作創(chuàng)建新的作用域,并且從中獲取需要的服務(wù)。
在正確的作用域中使用 ContocoDbContext
,即只能在請(qǐng)求作用域中使用該對(duì)象。
[HttpGet("/fire-and-forget-3")]public IActionResult FireAndForget3([FromServices]IServiceScopeFactory serviceScopeFactory){ _ = 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();} |
ASP.NET Core 不會(huì)緩沖 HTTP 響應(yīng)正文。 當(dāng)正文一旦開(kāi)始發(fā)送:
Header 就會(huì)與正文的數(shù)據(jù)包一起發(fā)送到客戶端。
此時(shí)就無(wú)法修改 header 了。
不要使用如下操作: 以下代碼嘗試在響應(yīng)啟動(dòng)后添加響應(yīng)頭:
app.Use(async (context, next) =>{ await next(); context.Response.Headers["test"] = "test value";}); |
在上述的代碼中,如果 next()
已經(jīng)開(kāi)始寫入響應(yīng),則 context.Response.Headers["test"] = "test value";
將會(huì)拋出異常。
應(yīng)該采用如下操作: 以下示例檢查 HTTP 響應(yīng)在修改 Header 之前是否已啟動(dòng)。
app.Use(async (context, next) =>{ await next(); if (!context.Response.HasStarted) { context.Response.Headers["test"] = "test value"; }}); |
應(yīng)該采用如下操作: 以下示例使用 HttpResponse.OnStarting
來(lái)設(shè)置 Header,這樣便可以在響應(yīng)啟動(dòng)時(shí)將 Header 一次性寫入到客戶端。
通過(guò)這種方式,響應(yīng)頭將在響應(yīng)開(kāi)始時(shí)調(diào)用已注冊(cè)的回調(diào)進(jìn)行一次性寫入。 如此這般便可以:
在恰當(dāng)?shù)臅r(shí)候進(jìn)行響應(yīng)頭的修改或者覆蓋。
不需要了解管道中的下一個(gè) middleware 的行為。
app.Use(async (context, next) =>{ context.Response.OnStarting(() => { context.Response.Headers["someheader"] = "somevalue"; return Task.CompletedTask; }); await next();}); |
僅當(dāng)后續(xù)組件能夠處理響應(yīng)或時(shí)才調(diào)用它們,因此如果當(dāng)前已經(jīng)開(kāi)始寫入響應(yīng)主體,后續(xù)操作就已經(jīng)不再需要,并有可能引發(fā)異常情況。
使用 in-process 模式托管, ASP.NET Core 應(yīng)用程序?qū)⑴c IIS 工作進(jìn)程在同一進(jìn)程中運(yùn)行。 In-process 模式擁有比 out-of-process 更加優(yōu)秀的性能表現(xiàn),因?yàn)檫@樣不需要將請(qǐng)求通過(guò)回環(huán)網(wǎng)絡(luò)適配器進(jìn)行代理中轉(zhuǎn)。 回環(huán)網(wǎng)絡(luò)適配器是將本機(jī)發(fā)送的網(wǎng)絡(luò)流量重新轉(zhuǎn)回本機(jī)的的網(wǎng)絡(luò)適配器。 IIS 進(jìn)程管理由 Windows Process Activation Service (WAS) 來(lái)完成。
在 ASP.NET Core 3.0 和更高版本中的默認(rèn)將采用 in-process 模式進(jìn)行托管。
到此,相信大家對(duì)“ASP.NET Core性能優(yōu)化的方法是什么”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
當(dāng)前名稱:ASP.NETCore性能優(yōu)化的方法是什么
當(dāng)前鏈接:http://m.newbst.com/article24/gohjje.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站改版、外貿(mào)網(wǎng)站建設(shè)、全網(wǎng)營(yíng)銷推廣、移動(dòng)網(wǎng)站建設(shè)、電子商務(wù)、軟件開(kāi)發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)