簡體   English   中英

我為什么要返回任務<iactionresult>在 Controller 中?</iactionresult>

[英]Why should I return Task<IActionResult> in a Controller?

因此,我已經嘗試掌握了相當長的一段時間,但看不到將每個控制器端點聲明為異步方法的意義。

讓我們看一個 GET-Request 來可視化這個問題。

這是我通過簡單請求到達 go 的方式,只需完成工作並發送響應即可。

[HttpGet]
public IActionResult GetUsers()
{
      // Do some work and get a list of all user-objects.
      List<User> userList = _dbService.ReadAllUsers(); 
      return Ok(userList);
} 

下面是我經常看到的async Task<IActionResult>選項,它與上面的方法相同,但方法本身返回一個Task 有人可能會認為,這個更好,因為您可以有多個請求進入並且它們異步處理但我測試了這種方法和上面的方法,結果相同。 兩者都可以同時接受多個請求。 那么我為什么要選擇這個簽名而不是上面的那個呢? 我只能看到這樣的負面影響,例如由於異步而將代碼轉換為狀態機。

[HttpGet]
public async Task<IActionResult> GetUsers()
{
      // Do some work and get a list of all user-objects.
      List<User> userList = _dbService.ReadAllUsers(); 
      return Ok(userList);
} 

下面的這種方法也是我無法理解的。 我看到很多代碼都具有這種設置。 他們await然后返回結果的一種async方法。 像這樣等待會使代碼再次順序化,而不是獲得多任務/多線程的好處。 這個我錯了嗎?

[HttpGet]
public async Task<IActionResult> GetUsers()
{
      // Do some work and get a list of all user-objects.
      List<User> userList = await _dbService.ReadAllUsersAsync(); 
      return Ok(userList);
} 

如果您能用事實啟發我,那就太好了,這樣我就可以像現在一樣繼續發展,或者知道由於誤解了這個概念而我做錯了。

如果您的數據庫服務 class 具有獲取用戶的async方法,那么您應該會看到好處。 一旦請求發送到數據庫,它就會等待另一端服務返回的網絡或磁盤響應。 在執行此操作時,可以釋放線程來執行其他工作,例如為其他請求提供服務。 在非異步版本中,線程只會阻塞並等待響應。

使用async controller 操作,您還可以獲得一個CancellationToken ,如果由於另一端的客戶端已終止連接而發出令牌信號,您可以提前退出(但這可能不適用於所有 web 服務器)。

[HttpGet]
public async Task<IActionResult> GetUsers(CancellationToken ct)
{
    ct.ThrowIfCancellationRequested();

    // Do some work and get a list of all user-objects.
    List<User> userList = await _dbService.ReadAllUsersAsync(ct); 
    return Ok(userList);
} 

因此,如果您有一組昂貴的操作並且客戶端斷開連接,您可以停止處理請求,因為客戶端永遠不會收到它。 這釋放了應用程序的時間和資源來處理客戶端對返回結果感興趣的請求。

但是,如果您有一個不需要async調用的非常簡單的操作,那么我可能不會擔心它,並保持原樣。

請閱讀ASP.NET 上的異步/等待簡介的“同步與異步請求處理”部分

兩者都可以同時接受多個請求。

是的。 這是因為 ASP.NET 是多線程的。 因此,在同步情況下,您只有多個線程調用相同的操作方法(在不同的 controller 實例上)。

對於非多線程平台(例如,Node.js),您必須使代碼異步處理同一進程中的多個請求。 但在 ASP.NET 上,它是可選的。

像這樣等待會使代碼再次順序化,而不是獲得多任務/多線程的好處。

是的,它是順序的,但它不是同步的。 從某種意義上說,它是順序的,即async方法一次執行一個語句,並且該請求在async方法完成之前不會完成。 但它不是同步的——同步代碼也是順序的,但它會阻塞一個線程,直到方法完成。

那么我為什么要選擇這個簽名而不是上面的那個呢?

如果您的后端可以擴展,那么異步操作方法的好處就是可擴展性。 具體來說,異步操作方法在異步操作進行時產生它們的線程 - 在這種情況下, GetUsers在數據庫執行其查詢時不占用線程。

在測試環境中很難看到好處,因為您的服務器有空閑線程,所以調用異步方法 10 次(占用 0 個線程)和調用同步方法 10 次(占用 10 個線程)之間沒有明顯的區別,還有 54 個備用)。 您可以人為地限制 ASP.NET 服務器中的線程數,然后進行一些測試以查看差異。

在現實世界的服務器中,您通常希望使其盡可能異步,以便您的線程可用於處理其他請求。 或者,如此所述:

請記住,異步代碼不會取代線程池。 這不是線程池異步代碼; 它是線程池異步代碼。 異步代碼允許您的應用程序充分利用線程池。 它采用現有的線程池並將其增加到 11。

請記住上面的“ if ”; 這尤其適用於現有代碼。 如果您只有一個 SQL 服務器后端,並且幾乎所有操作都查詢數據庫,那么將它們更改為異步可能沒有用,因為可伸縮性瓶頸通常是數據庫服務器而不是 web 服務器。 但是,如果您的 web 應用程序可以使用線程來處理非數據庫請求,或者如果您的數據庫后端是可擴展的(NoSQL、SQL Azure 等,則可能會有所幫助)。

對於新代碼,我默認推薦異步方法。 異步可以更好地利用服務器資源並且對雲更友好(即,按需付費托管成本更低)。

你在這里缺少一些基本的東西。

當您使用async Task<>時,您實際上是在說“異步運行所有 I/O 代碼並釋放處理線程以...處理”。

在規模上,您的應用程序將能夠每秒處理更多請求,因為您的 I/O 不會占用您的 CPU 密集型工作,反之亦然。

您現在沒有看到太多好處,因為您可能正在使用大量資源進行本地測試。

如您所知,ASP.NET 是基於多線程 model 的請求執行。 簡而言之,每個請求都在自己的線程中運行,這與其他單線程執行方法(例如 nodejs 中的事件循環)相反。

現在,線程是一種有限資源。 如果您耗盡了線程池(可用線程),您將無法繼續有效地處理任務(或者在大多數情況下根本無法)。 如果您對操作處理程序使用同步執行,則您會占用池中的這些線程(即使它們不需要)。 重要的是要了解這些線程處理請求,它們不進行輸入/輸出。 I/O 由獨立於 ASP.NET 請求線程的獨立進程處理。 因此,如果在您的操作中,您有一個數據庫提取操作需要 15 秒才能執行,那么您將強制當前線程等待空閑15 秒以返回數據庫結果,以便它可以繼續執行代碼。 因此,如果您有 50 個這樣的請求,那么您將在基本睡眠模式下占用 50 個線程。 顯然,這不是非常可擴展的,並且很快就會成為一個問題。 為了有效地使用您的資源,最好在到達 I/O 操作時保留正在執行的請求的 state,同時釋放線程以處理另一個請求。 當 I/O 操作完成時,state 被重新組裝並提供給池中的空閑線程以恢復處理。 這就是您使用異步處理的原因。 它可以讓您更有效地使用有限資源並防止此類線程飢餓。 當然,這不是最終的解決方案。 它可以幫助您擴展應用程序以獲得更高的負載。 如果您不需要它,請不要使用它,因為它只會增加開銷。

異步操作返回一個任務(它不是結果,而是一個 promise,一旦任務完成就會有結果。

異步方法應該是await ed,以便它等待任務完成。 如果您await異步方法,則返回值將不再是任務,而是您預期的結果。

想象一下這個異步方法:

async Task<SomeResult> SomeMethod()
{
    ...
} 

以下代碼:

var result = SomeMethod(); // the result is a Task<SomeResult> which may have not completed yet

var result = await SomeMethod(); // the result is type of SomeResult

現在,為什么我們使用async方法而不是同步方法:

想象一下,一個方法正在做一些可能需要很長時間才能完成的工作,當它完成同步時,所有其他請求將等待直到這個耗時工作的執行完成,因為它們都發生在同一個線程中。 但是,當它是異步的時,任務將在一個(可能是另一個)線程中完成,並且不會阻塞其他請求。

結果從一個異步(非阻塞)操作返回一個任務,該操作代表一些應該完成的工作。 任務可以告訴您工作是否已完成,如果操作返回結果,則任務會為您提供在任務完成之前不可用的結果。 您可以從官方 Microsoft Docs 了解有關 C# 異步編程的更多信息:

https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=net-5.0

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM