簡體   English   中英

為什么要使用 async 並返回 await,什么時候可以返回 Task<t> 直接地?</t>

[英]Why use async and return await, when you can return Task<T> directly?

沒有這樣寫方法的場景:

public async Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return await DoAnotherThingAsync();
}

而不是這個:

public Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return DoAnotherThingAsync();
}

有道理嗎?

當您可以直接從內部DoAnotherThingAsync()調用返回Task<T>時,為什么要使用return await構造?

我在很多地方看到帶有return await的代碼,我想我可能漏掉了什么。 但據我了解,在這種情況下不使用 async/await 關鍵字並直接返回 Task 在功能上是等效的。 為什么要增加額外await層的額外開銷?

當在普通方法中return和在async方法中return await表現不同時,有一種偷偷摸摸的情況:當與 using 結合using時(或者更一般地說,在try塊中的任何return await )。

考慮這兩個版本的方法:

Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return foo.DoAnotherThingAsync();
    }
}

async Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return await foo.DoAnotherThingAsync();
    }
}

第一個方法將在DoAnotherThingAsync()方法返回后立即Dispose() Foo對象,這可能在它實際完成之前很久。 這意味着第一個版本可能有問題(因為Foo處理得太快了),而第二個版本可以正常工作。

如果您不需要async (即,您可以直接返回Task ),則不要使用async

在某些情況下return await很有用,比如你有兩個異步操作要做:

var intermediate = await FirstAsync();
return await SecondAwait(intermediate);

有關async性能的更多信息,請參閱 Stephen Toub 的MSDN 文章和有關該主題的視頻

更新:我寫了一篇更詳細的 博客文章

您想要這樣做的唯一原因是早期代碼中是否存在其他await ,或者您在返回結果之前以某種方式操作結果。 可能發生這種情況的另一種方式是通過改變異常處理方式的try/catch 如果您沒有這樣做,那么您是對的,沒有理由增加使方法async的開銷。

您可能需要等待結果的另一種情況是:

async Task<IFoo> GetIFooAsync()
{
    return await GetFooAsync();
}

async Task<Foo> GetFooAsync()
{
    var foo = await CreateFooAsync();
    await foo.InitializeAsync();
    return foo;
}

在這種情況下, GetIFooAsync()必須等待GetFooAsync的結果,因為兩個方法之間的T類型不同,並且Task<Foo>不能直接分配給Task<IFoo> 但是,如果您等待結果,它就會變成Foo可以直接分配給IFoo 然后異步方法只是將結果重新打包到Task<IFoo> ,然后你就走了。

如果您不使用 return await 您可能會在調試時或在異常日志中打印時破壞您的堆棧跟蹤。

當您返回任務時,該方法實現了它的目的,並且它不在調用堆棧中。 當您使用return await時,您會將其留在調用堆棧中。

例如:

使用 await 時調用堆棧:A 等待 B 的任務 => B 等待 C 的任務

使用 await 時調用堆棧:A 等待來自 C 的任務,而 B 已返回。

使原本簡單的“thunk”方法異步在內存中創建一個異步狀態機,而非異步則沒有。 雖然這通常可以指出人們使用非異步版本,因為它更有效(這是真的),但這也意味着在掛起的情況下,您沒有證據表明該方法涉及“返回/繼續堆棧”這有時會讓人更難理解這個問題。

所以是的,當性能不重要時(通常不是),我將在所有這些 thunk 方法上拋出異步,以便我有異步狀態機來幫助我稍后診斷掛起,並幫助確保如果那些thunk 方法會隨着時間的推移而發展,它們肯定會返回錯誤的任務而不是 throw。

這也讓我感到困惑,我覺得以前的答案忽略了你的實際問題:

當您可以從內部 DoAnotherThingAsync() 調用中直接返回 Task 時,為什么要使用 return await 構造?

好吧,有時您實際上想要一個Task<SomeType> ,但大多數時候您實際上想要一個SomeType的實例,即任務的結果。

從您的代碼:

async Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return await foo.DoAnotherThingAsync();
    }
}

不熟悉語法的人(例如我)可能會認為這個方法應該返回一個Task<SomeResult> ,但由於它被標記為async ,這意味着它的實際返回類型是SomeResult 如果您只使用return foo.DoAnotherThingAsync() ,您將返回一個無法編譯的任務。 正確的做法是返回任務的結果,所以return await

您可能想要return await的另一個原因: await語法支持Task<T>ValueTask<T>返回類型之間的自動轉換。 例如,即使 SubTask 方法返回Task<T>但其調用者返回ValueTask<T> ,下面的代碼仍然有效。

async Task<T> SubTask()
{
...
}

async ValueTask<T> DoSomething()
{
  await UnimportantTask();
  return await SubTask();
}

如果您跳過DoSomething()行上的 await,您將收到編譯器錯誤 CS0029:

無法將類型“System.Threading.Tasks.Task<BlaBla>”隱式轉換為“System.Threading.Tasks.ValueTask<BlaBla>”。

如果您嘗試顯式類型轉換,您將獲得 CS0030。 不要打擾。

順便說一下,這是 .NET Framework。 我完全可以預見到評論說“在 .NET hypothetical_version中已修復”,我還沒有測試過。 :)

非等待方法的另一個問題是有時您不能隱式轉換返回類型,尤其是對於Task<IEnumerable<T>>

async Task<List<string>> GetListAsync(string foo) => new();

// This method works
async Task<IEnumerable<string>> GetMyList() => await GetListAsync("myFoo");

// This won't work
Task<IEnumerable<string>> GetMyListNoAsync() => GetListAsync("myFoo");

錯誤:

無法將類型“System.Threading.Tasks.Task<System.Collections.Generic.List>”隱式轉換為“System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable>”

暫無
暫無

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

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