簡體   English   中英

這些等待方法有什么區別?

[英]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.

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