[英]What is the difference between these awaitable methods?
我正在研究C#中的一些異步編程,並想知道這些函數之間的區別在於它們完全相同並且都是等待的。
public Task<Bar> GetBar(string fooId)
{
return Task.Run(() =>
{
var fooService = new FooService();
var bar = fooService.GetBar(fooId);
return bar;
});
}
public Task<Bar> GetBar(string fooId)
{
var fooService = new FooService();
var bar = fooService.GetBar(fooId);
return Task.FromResult(bar)
}
public async Task<Bar> GetBar(string fooId)
{
return await Task.Run(() =>
{
var fooService = new FooService();
var bar = fooService.GetBar(fooId);
return bar;
});
}
我的猜測是,第一種是正確的做事方式,並且在您嘗試從返回的Task獲取結果之前,代碼不會執行。
在第二種情況下,代碼在調用時執行,結果存儲在返回的任務中。
第三種有點像第二種? 代碼是在調用時執行的,Task.Run的結果是返回? 在這種情況下,這個函數會有點愚蠢嗎?
我是對的還是離開的?
這些方法實現都沒有意義。 您所做的就是將阻塞工作推送到線程池(或者更糟糕的是,同步運行它並將結果包裝在Task<Bar>
實例中)。 你應該做的是暴露同步API,讓調用者決定如何調用它。 他們是否想要使用Task.Run
然后由他們決定。
話雖如此,這里有不同之處:
#1
第一個變體(返回通過Task.Run
直接創建的Task<Bar>
)是“最純粹的”,即使它從API的角度來看沒有多大意義。 您允許Task.Run
在線程池上安排給定的工作,並將表示異步操作完成的Task<Bar>
返回給調用者。
#2
第二種方法(利用Task.FromResult
) 不是異步的。 它同步執行,就像常規方法調用一樣。 結果只包含在已完成的Task<Bar>
實例中。
#3
這是第一個更復雜的版本。 你正在實現類似於#1的結果,但是有額外的,不必要的,甚至有點危險的await
。 這個值得更詳細地看一下。
async/await
非常適合鏈接異步操作,通過將表示異步工作的多個Task
組合到一個單元中( Task
)。 它可以幫助您按正確的順序進行操作,為您提供異步操作之間豐富的控制流,並確保在正確的線程上發生事情。
但是,上述情況都不會對您的方案有任何好處,因為您只有一個Task
。 因此,沒有必要讓編譯器生成一個狀態機,只是為了完成Task.Run
已經完成的工作。
設計糟糕的async
方法也很危險。 通過在await
ed Task
上不使用ConfigureAwait(false)
,您無意中引入了SynchronizationContext
捕獲,從而導致性能下降並引入死鎖風險,從而無益。
如果你的調用者決定在具有SynchronizationContext
(即Win Forms,WPF和可能的 ASP.NET)的環境中通過GetBar(fooId).Wait()
或GetBar(fooId).Result
阻止你的Task<Bar>
GetBar(fooId).Result
,他們將因為這里討論的原因而陷入僵局。
我在Stackoverflow上的某個地方閱讀了以下類比的評論。 因為它在評論中我找不到它,所以沒有鏈接。
假設你必須做早餐。 你煮一些雞蛋,烤面包。
如果你開始煮雞蛋,然后在子程序“煮雞蛋”的某個地方,你將不得不等到雞蛋煮沸
同步將是你等到雞蛋煮沸完畢后再啟動子程序“吐司面包”。
然而,如果在煮雞蛋時,你不要等待,但是你開始烘烤雞蛋會更有效率。 然后你等待其中任何一個完成,並繼續“煮雞蛋”或“吐司面包”的過程,無論先完成。 這是異步但不並發。 仍然是一個人在做所有事情。
第三種方法是聘請一位煮雞蛋的廚師,同時敬酒面包。 這實際上是並發的:兩個人正在做某事。 如果你真的很有錢,你也可以租用烤面包機,而你看報紙,但是,嘿,我們並不都住在唐頓庄園;-)
回到你的問題。
Nr 2:是同步的:主線程完成所有工作。 雞蛋煮沸后,此線程返回,然后調用者可以執行任何其他操作。
Nr 1未聲明為異步。 這意味着雖然你啟動了另一個可以完成工作的線程,但是你的來電者不能繼續做其他事情,雖然你可以,但是你沒有,你只需要等到雞蛋煮沸。
第三個過程聲明為異步。 這意味着,只要等待雞蛋開始,你的來電者就可以做其他事情,比如敬酒面包。 請注意,這項工作所有工作都由一個線程完成。
如果你的調用者不等待你做任何事情而是等待你,除非你的調用者也被聲明為異步,否則它將沒有多大用處。 這將使呼叫者的呼叫者有機會做其他事情。
通常在正確使用async-await時,您會看到以下內容: - 聲明為異步的每個函數都返回Task而不是void,而Task <TResult>而不是TResult - 只有一個例外:事件處理程序返回void而不是Task。 - 調用異步函數的每個函數都應該聲明為異步,否則使用async-await並不是很有用 - 調用異步方法后,你可以在煮蛋時開始烘烤面包。 當面包烤好時,你可以等待雞蛋,或者你可以在烘烤過程中檢查雞蛋是否准備好,或者最有效的是等待Task.WhenAny,繼續完成雞蛋或吐司或等待任務。當你沒有任何有用的東西時,只要不是兩個都完成了。
希望這個比喻有所幫助
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.