[英]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
處理得太快了),而第二個版本可以正常工作。
您想要這樣做的唯一原因是早期代碼中是否存在其他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.