狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

理解C#中的ValueTask

admin
2024年5月1日 19:32 本文熱度 954
英文:devblogs.microsoft.com

譯文:cnblogs.com/xiaoxiaotank/p/13206569.html

譯者:xiaoxiaotank

前言


Task類是在.NET Framework 4引入的,位于System.Threading.Tasks命名空間下,它與派生的泛型類Task<TResult>已然成為.NET編程的主力,也是以async/await(C# 5引入的)語(yǔ)法糖為代表的異步編程模型的核心。


隨后,我會(huì)向大家介紹.NET Core 2.0中的新成員ValueTask/ValueTask<TResult>,來(lái)幫助你在日常開(kāi)發(fā)用例中降低內(nèi)存分配開(kāi)銷(xiāo),提升異步性能。


一、Task


雖然Task的用法有很多,但其最核心的是“承諾(promise)”,用來(lái)表示某個(gè)操作最終完成。


當(dāng)你初始化一個(gè)操作后,會(huì)獲取一個(gè)與該操作相關(guān)的Task,當(dāng)這個(gè)操作完成時(shí),Task也同樣會(huì)完成。這個(gè)操作的完成情況可能有以下幾種:


  • 作為初始化操作的一部分同步完成,例如:訪問(wèn)一些已被緩存的數(shù)據(jù)


  • 恰好在你獲取到Task實(shí)例的時(shí)候異步完成,例如:訪問(wèn)雖然沒(méi)被緩存但是訪問(wèn)速度非常快的數(shù)據(jù)


  • 你已經(jīng)獲取到了Task實(shí)例,并等待了一段時(shí)間后,才異步完成,例如:訪問(wèn)一些網(wǎng)絡(luò)數(shù)據(jù)


由于操作可能會(huì)異步完成,所以當(dāng)你想要使用最終結(jié)果時(shí),你可以通過(guò)阻塞來(lái)等待結(jié)果返回(不過(guò)這違背了異步操作的初衷);或者,使用回調(diào)方法,它會(huì)在操作完成時(shí)被調(diào)用,.NET 4通過(guò)Task.ContinueWith方法顯式實(shí)現(xiàn)了這個(gè)回調(diào)方法,如:


SomeOperationAsync().ContinueWith(task =>
{
   try
   {
       TResult result = task.Result;
       UseResult(result);
   }
   catch(Exception ex)
   {
       HandleException(ex);
   }
})


而在.NET 4.5中,Task通過(guò)結(jié)合await,大大簡(jiǎn)化了對(duì)異步操作結(jié)果的使用,它能夠優(yōu)化上面說(shuō)的所有情況,無(wú)論操作是同步完成、快速異步完成還是已經(jīng)(隱式地)提供回調(diào)之后異步完成,都不在話下,寫(xiě)法如下:


TResult result = await SomeOperationAsync();
UseResult(result);


Task作為一個(gè)類(class),非常靈活,并因此帶來(lái)了很多好處。例如:


  • 它可以被任意數(shù)量的調(diào)用者并發(fā)await多次


  • 你可以把它存儲(chǔ)到字典中,以便任意數(shù)量的后續(xù)使用者對(duì)其進(jìn)行await,進(jìn)而把這個(gè)字典當(dāng)成異步結(jié)果的緩存


  • 如果需要的話,你可以通過(guò)阻塞等待操作完成


  • 另外,你還可以對(duì)Task使用各種各樣的操作(稱為“組合器”,combinators),例如使用Task.WhenAny異步等待任意一個(gè)操作率先完成。


不過(guò),在大多數(shù)情況下其實(shí)用不到這種靈活性,只需要簡(jiǎn)單地調(diào)用異步操作并await獲取結(jié)果就好了:


TResult result = await SomeOperationAsync();
UseResult(result);


在這種用法中,我們不需要多次await task,不需要處理并發(fā)await,不需要處理同步阻塞,也不需要編寫(xiě)組合器,我們只是異步等待操作的結(jié)果。


這就是我們編寫(xiě)同步代碼(例如TResult result = SomeOperation())的方式,它很自然地轉(zhuǎn)換為了async/await的方式。


此外,Task也確實(shí)存在潛在缺陷,特別是在需要?jiǎng)?chuàng)建大量Task實(shí)例且要求高吞吐量和高性能的場(chǎng)景下。Task 是一個(gè)類(class),作為一個(gè)類,這意味著每創(chuàng)建一個(gè)操作,都需要分配一個(gè)對(duì)象,而且分配的對(duì)象越多,垃圾回收器(GC)的工作量也會(huì)越大,我們花在這個(gè)上面的資源也就越多,本來(lái)這些資源可以用于做其他事情。慶幸的是,運(yùn)行時(shí)(Runtime)和核心庫(kù)在許多情況下都可以緩解這種情況。


例如,你寫(xiě)了如下方法:


public async Task WriteAsync(byte value)
{
   if (_bufferedCount == _buffer.Length)
   {
       await FlushAsync();
   }
   _buffer[_bufferedCount++] = value;
}


一般來(lái)說(shuō),緩沖區(qū)中會(huì)有可用空間,也就無(wú)需Flush,這樣操作就會(huì)同步完成。這時(shí),不需要Task返回任何特殊信息,因?yàn)闆](méi)有返回值,返回Task與同步方法返回void沒(méi)什么區(qū)別。因此,運(yùn)行時(shí)可以簡(jiǎn)單地緩存單個(gè)非泛型Task,并將其反復(fù)用作任何同步完成的方法的結(jié)果(該單例是通過(guò)Task.CompletedTask公開(kāi)的)。


或者,你的方法是這樣的:


public async Task<bool> MoveNextAsync()
{
   if (_bufferedCount == 0)
   {
       // 緩存數(shù)據(jù)
       await FillBuffer();
   }
   return _bufferedCount > 0;
}


一般來(lái)說(shuō),我們想的是會(huì)有一些緩存數(shù)據(jù),這樣_bufferedCount就不會(huì)等于0,直接返回true就可以了;只有當(dāng)沒(méi)有緩存數(shù)據(jù)(即_bufferedCount == 0)時(shí),才需要執(zhí)行可能異步完成的操作。而且,由于只有true和false這兩種可能的結(jié)果,所以只需要兩個(gè)Task<bool>對(duì)象來(lái)分別表示true和false,因此運(yùn)行時(shí)可以將這兩個(gè)對(duì)象緩存下來(lái),避免內(nèi)存分配。只有當(dāng)操作異步完成時(shí),該方法才需要分配新的Task<bool>,因?yàn)檎{(diào)用方在知道操作結(jié)果之前,就要得到Task<bool>對(duì)象,并且要求該對(duì)象是唯一的,這樣在操作完成后,就可以將結(jié)果存儲(chǔ)到該對(duì)象中。


運(yùn)行時(shí)也為其他類型型維護(hù)了一個(gè)類似的小型緩存,但是想要緩存所有內(nèi)容是不切實(shí)際的。例如下面這個(gè)方法:


public async Task<int> ReadNextByteAsync()
{
   if (_bufferedCount == 0)
   {
      await FillBuffer();
   }
   if (_bufferedCount == 0)
   {
       return -1;
   }
   _bufferedCount--;
   return _buffer[_position++];
}


通常情況下,上面的案例也會(huì)同步完成。但是與上一個(gè)返回Task<bool>的案例不同,該方法返回的Int32的可能值約有40億個(gè)結(jié)果,如果將它們都緩存下來(lái),大概會(huì)消耗數(shù)百GB的內(nèi)存。雖然運(yùn)行時(shí)保留了一個(gè)小型緩存,但也只保留了一小部分結(jié)果值,因此,如果該方法同步完成(緩沖區(qū)中有數(shù)據(jù))的返回值是4,它會(huì)返回緩存的Task<int>,但是如果它同步完成的返回值是42,那就會(huì)分配一個(gè)新的Task<int>,相當(dāng)于調(diào)用了Task.FromResult(42)。


許多框架庫(kù)的實(shí)現(xiàn)也嘗試通過(guò)維護(hù)自己的緩存來(lái)進(jìn)一步緩解這種情況。


例如,.NET Framework 4.5中引入的MemoryStream.ReadAsync重載方法總是會(huì)同步完成,因?yàn)樗粡膬?nèi)存中讀取數(shù)據(jù)。它返回一個(gè)Task<int>對(duì)象,其中Int32結(jié)果表示讀取的字節(jié)數(shù)。


ReadAsync常常用在循環(huán)中,并且每次調(diào)用時(shí)請(qǐng)求的字節(jié)數(shù)是相同的(僅讀取到數(shù)據(jù)末尾時(shí)才有可能不同)。


因此,重復(fù)調(diào)用通常會(huì)返回同步結(jié)果,其結(jié)果與上一次調(diào)用相同。這樣,可以維護(hù)單個(gè)Task實(shí)例的緩存,即緩存最后一次成功返回的Task實(shí)例。


然后在后續(xù)調(diào)用中,如果新結(jié)果與其緩存的結(jié)果相匹配,它還是返回緩存的Task實(shí)例;否則,它會(huì)創(chuàng)建一個(gè)新的Task實(shí)例,并把它作為新的緩存Task,然后將其返回。


即使這樣,在許多操作同步完成的情況下,仍需強(qiáng)制分配Task<TResult>實(shí)例并返回。


二、同步完成時(shí)的ValueTask<TResult>


正因如此,在.NET Core 2.0 中引入了一個(gè)新類型——ValueTask<TResult>,用來(lái)優(yōu)化性能。之前的.NET版本可以通過(guò)引用NuGet包使用:


System.Threading.Tasks.Extensions


ValueTask<TResult>是一個(gè)結(jié)構(gòu)體(struct),用來(lái)包裝TResult或Task<TResult>,因此它可以從異步方法中返回。并且,如果方法是同步成功完成的,則不需要分配任何東西:我們可以簡(jiǎn)單地使用TResult來(lái)初始化ValueTask<TResult>并返回它。只有當(dāng)方法異步完成時(shí),才需要分配一個(gè)Task<TResult>實(shí)例,并使用ValueTask<TResult>來(lái)包裝該實(shí)例。


另外,為了使ValueTask<TResult>更加輕量化,并為成功情形進(jìn)行優(yōu)化,所以拋出未處理異常的異步方法也會(huì)分配一個(gè)Task<TResult>實(shí)例,以方便ValueTask<TResult>包裝Task<TResult>,而不是增加一個(gè)附加字段來(lái)存儲(chǔ)異常(Exception)。


這樣,像MemoryStream.ReadAsync這類方法將返回ValueTask<int>而不需要關(guān)注緩存,現(xiàn)在可以使用以下代碼:


public override ValueTask<int> ReadAsync(byte[] buffer, int offset, int count)
{
   try
   {
       int bytesRead = Read(buffer, offset, count);
       return new ValueTask<int>(bytesRead);
   }
   catch (Exception e)
   {
      return new ValueTask<int>(Task.FromException<int>(e));
   }
}


三、異步完成時(shí)的ValueTask<TResult>


能夠編寫(xiě)出在同步完成時(shí)無(wú)需為結(jié)果類型產(chǎn)生額外內(nèi)存分配的異步方法是一項(xiàng)很大的突破,.NET Core 2.0引入ValueTask<TResult>的目的,就是將頻繁使用的新方法定義為返回ValueTask<TResult>而不是Task<TResult>。


例如,我們?cè)?NET Core 2.1中的Stream類中添加了新的ReadAsync重載方法,以傳遞Memory<byte>來(lái)替代byte[],該方法的返回類型就是ValueTask<int>。


這樣,Streams(一般都有一種同步完成的ReadAsync方法,如前面的MemoryStream示例中所示)現(xiàn)在可以在使用過(guò)程中更少的分配內(nèi)存。


但是,在處理高吞吐量服務(wù)時(shí),我們依舊需要考慮如何盡可能地避免額外內(nèi)存分配,這就要想辦法減少或消除異步完成時(shí)的內(nèi)存分配。


使用await異步編程模型時(shí),對(duì)于任何異步完成的操作,我們都需要返回代表該操作最終完成的對(duì)象:調(diào)用者需要能夠傳遞在操作完成時(shí)調(diào)用的回調(diào)方法,這就要求在堆上有一個(gè)唯一的對(duì)象,用作這種特定操作的管道,但是,這并不意味著有關(guān)操作完成后能否重用該對(duì)象的任何信息。如果對(duì)象可以重復(fù)使用,則API可以維護(hù)一個(gè)或多個(gè)此類對(duì)象的緩存,并將其復(fù)用于序列化操作,也就是說(shuō),它不能將同一對(duì)象用于多個(gè)同時(shí)進(jìn)行中的異步操作,但可以復(fù)用于非并行訪問(wèn)下的對(duì)象。


在.NET Core 2.1中,為了支持這種池化和復(fù)用,ValueTask<TResult>進(jìn)行了增強(qiáng),不僅可以包裝TResult和Task<TResult>,還可以包裝新引入的接口IValueTaskSource<TResult>。


類似于Task<TResult>,IValueTaskSource<TResult>提供表示異步操作所需的核心支持;


public interface IValueTaskSource<out TResult>
{
   ValueTaskSourceStatus GetStatus(short token);
   void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags);
   TResult GetResult(short token);
}


  • GetStatus用于實(shí)現(xiàn)諸如ValueTask<TResult>.IsCompleted之類的屬性,返回指示異步操作是否仍在掛起或是否已完成以及完成情況(成功或失敗)的指示。


  • OnCompleted用于ValueTask<TResult>的等待者(awaiter),它與調(diào)用者提供的回調(diào)方法掛鉤,當(dāng)異步操作完成時(shí),等待者繼續(xù)執(zhí)行回調(diào)方法。


  • GetResult用于檢索操作的結(jié)果,以便在操作完成后,等待者可以獲取TResult或傳播可能發(fā)生的任何異常。


大多數(shù)開(kāi)發(fā)人員永遠(yuǎn)都不需要用到此接口(指IValueTaskSource<TResult>):方法只是簡(jiǎn)單地將包裝該接口實(shí)例的ValueTask<TResult>實(shí)例返回給調(diào)用者,而調(diào)用者并不需要知道內(nèi)部細(xì)節(jié)。該接口的主要作用是為了讓開(kāi)發(fā)人員在編寫(xiě)性能敏感的API時(shí)可以盡可能地避免額外內(nèi)存分配。


.NET Core 2.1中有幾個(gè)類似的API。


最值得關(guān)注的是Socket.ReceiveAsync和Socket.SendAsync,添加了新的重載,例如:


public ValueTask<int> ReceiveAsync(Memory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default);


此重載返回ValueTask<int>。


如果操作同步完成,則可以簡(jiǎn)單地構(gòu)造具有正確結(jié)果的ValueTask<int>,例如:


int result = …;
return new ValueTask<int>(result);


如果它異步完成,則可以使用實(shí)現(xiàn)此接口的池對(duì)象:


IValueTaskSource<int> vts = …;
return new ValueTask<int>(vts);


該Socket實(shí)現(xiàn)維護(hù)了兩個(gè)這樣的池對(duì)象,一個(gè)用于Receive,一個(gè)用于Send,這樣,每次未完成的對(duì)象只要不超過(guò)一個(gè),即使這些重載是異步完成的,它們最終也不會(huì)額外分配內(nèi)存。NetworkStream也因此受益。


例如,在.NET Core 2.1中,Stream公開(kāi)了一個(gè)方法:


public virtual ValueTask<int> ReadAsync
(Memory<byte> buffer, CancellationToken cancellationToken = default)
;


NetworkStream的重載方法NetworkStream.ReadAsync,內(nèi)部實(shí)際邏輯只是交給了Socket.ReceiveAsync去處理,所以將優(yōu)勢(shì)從Socket帶到了NetworkStream中,使得NetworkStream.ReadAsync也有效地不進(jìn)行額外內(nèi)存分配了。


四、非泛型的ValueTask


當(dāng)在.NET Core 2.0中引入ValueTask<TResult>時(shí),它純粹是為了優(yōu)化異步方法同步完成的情況——避免必須分配一個(gè)Task<TResult>實(shí)例用于存儲(chǔ)TResult。這也意味著非泛型的ValueTask是不必要的(因?yàn)闆](méi)有TResult):對(duì)于同步完成的情況,返回值為T(mén)ask的方法可以返回Task.CompletedTask單例,此單例由async Task方法的運(yùn)行時(shí)隱式返回。


然而,隨著即使異步完成也要避免額外內(nèi)存分配需求的出現(xiàn),非泛型的ValueTask又變得必不可少。


因此,在.NET Core 2.1中,我們還引入了非泛型的ValueTask和IValueTaskSource。它們提供泛型版本對(duì)應(yīng)的非泛型版本,使用方式類似,只是GetResult返回void。


五、實(shí)現(xiàn)IValueTaskSource/IValueTaskSource<TResult>


大多數(shù)開(kāi)發(fā)人員都不需要實(shí)現(xiàn)這兩個(gè)接口,它們也不是特別容易實(shí)現(xiàn)。如果您需要的話,.NET Core 2.1的內(nèi)部有幾種實(shí)現(xiàn)可以用作參考,例如


  • AwaitableSocketAsyncEventArgs


  • AsyncOperation


  • DefaultPipeReader


為了使想要這樣做的開(kāi)發(fā)人員更輕松地進(jìn)行開(kāi)發(fā),將在.NET Core 3.0中計(jì)劃引入ManualResetValueTaskSourceCore<TResult>結(jié)構(gòu)體(譯注:目前已引入),用于實(shí)現(xiàn)接口的所有邏輯,并可以被包裝到其他實(shí)現(xiàn)了IValueTaskSource和IValueTaskSource<TResult>的包裝器對(duì)象中,這個(gè)包裝器對(duì)象只需要單純地將大部分實(shí)現(xiàn)交給該結(jié)構(gòu)體就可以了。


六、ValueTask的有效消費(fèi)模式


從表面上看,ValueTask和ValueTask<TResult>的使用限制要比Task和Task<TResult>大得多 。不過(guò)沒(méi)關(guān)系,這甚至就是我們想要的,因?yàn)橹饕南M(fèi)方式就是簡(jiǎn)單地await它們。


但是,由于ValueTask和ValueTask<TResult>可能包裝可復(fù)用的對(duì)象,因此,與Task和Task<TResult>相比,如果調(diào)用者偏離了僅await它們的設(shè)計(jì)目的,則它們?cè)谑褂蒙蠈?shí)際回受到很大的限制。通常,以下操作絕對(duì)不能用在ValueTask/ValueTask<TResult>上:


  • await ValueTask/ValueTask<TResult>多次。


因?yàn)榈讓訉?duì)象可能已經(jīng)被回收了,并已由其他操作使用。而Task/Task<TResult>永遠(yuǎn)不會(huì)從完成狀態(tài)轉(zhuǎn)換為未完成狀態(tài),因此您可以根據(jù)需要等待多次,并且每次都會(huì)得到相同的結(jié)果。


  • 并發(fā)await ValueTask/ValueTask<TResult>。


底層對(duì)象期望一次只有單個(gè)調(diào)用者的單個(gè)回調(diào)來(lái)使用,并且嘗試同時(shí)等待它可能很容易引入競(jìng)爭(zhēng)條件和細(xì)微的程序錯(cuò)誤。這也是第一個(gè)錯(cuò)誤操作的一個(gè)更具體的情況——await ValueTask/ValueTask<TResult>多次。相反,Task/Task<TResult>支持任意數(shù)量的并發(fā)等待


  • 使用.GetAwaiter().GetResult()時(shí)操作尚未完成。


IValueTaskSource / IValueTaskSource<TResult>接口的實(shí)現(xiàn)中,在操作完成前是沒(méi)有強(qiáng)制要求支持阻塞的,并且很可能不會(huì)支持,所以這種操作本質(zhì)上是一種競(jìng)爭(zhēng)狀態(tài),也不可能按照調(diào)用方的意愿去執(zhí)行。相反,Task/Task<TResult>支持此功能,可以阻塞調(diào)用者,直到任務(wù)完成。


如果您使用ValueTask/ValueTask<TResult>,并且您確實(shí)需要執(zhí)行上述任一操作,則應(yīng)使用.AsTask()獲取Task/Task<TResult>實(shí)例,然后對(duì)該實(shí)例進(jìn)行操作。并且,在之后的代碼中您再也不應(yīng)該與該ValueTask/ValueTask<TResult>進(jìn)行交互。


簡(jiǎn)單說(shuō)就是使用ValueTask/ValueTask<TResult>時(shí),您應(yīng)該直接await它(可以有選擇地加上.ConfigureAwait(false)),或直接調(diào)用AsTask()且再也不要使用它,例如:


// 以這個(gè)方法為例
public ValueTask<int> SomeValueTaskReturningMethodAsync();


// GOOD
int result = await SomeValueTaskReturningMethodAsync();

// GOOD
int result = await SomeValueTaskReturningMethodAsync().ConfigureAwait(false);

// GOOD
Task<int> t = SomeValueTaskReturningMethodAsync().AsTask();

// WARNING
ValueTask<int> vt = SomeValueTaskReturningMethodAsync();
... // 將實(shí)例存儲(chǔ)到本地會(huì)使它被濫用的可能性更大,
   // 不過(guò)這還好,適當(dāng)使用沒(méi)啥問(wèn)題

// BAD: await 多次
ValueTask<int> vt = SomeValueTaskReturningMethodAsync();
int result = await vt;
int result2 = await vt;

// BAD: 并發(fā) await (and, by definition then, multiple times)
ValueTask<int> vt = SomeValueTaskReturningMethodAsync();
Task.Run(async () => await vt);
Task.Run(async () => await vt);

// BAD: 在不清楚操作是否完成的情況下使用 GetAwaiter().GetResult()
ValueTask<int> vt = SomeValueTaskReturningMethodAsync();
int result = vt.GetAwaiter().GetResult();


另外,開(kāi)發(fā)人員可以選擇使用另一種高級(jí)模式,最好你在衡量后確定它可以帶來(lái)好處之后再使用。具體來(lái)說(shuō),ValueTask/ValueTask<TResult>確實(shí)公開(kāi)了一些與操作的當(dāng)前狀態(tài)有關(guān)的屬性,例如:


  • IsCompleted,如果操作尚未完成,則返回false;如果操作已完成,則返回true(這意味著該操作不再運(yùn)行,并且可能已經(jīng)成功完成或以其他方式完成)


  • IsCompletedSuccessfully,當(dāng)且僅當(dāng)它已完成并成功完成才返回true(意味著嘗試等待它或訪問(wèn)其結(jié)果不會(huì)導(dǎo)致引發(fā)異常)


舉個(gè)例子,對(duì)于一些執(zhí)行非常頻繁的代碼,想要避免在異步執(zhí)行時(shí)進(jìn)行額外的性能損耗,并在某個(gè)本質(zhì)上會(huì)使ValueTask/ValueTask<TResult>不再使用的操作(如await、.AsTask())時(shí),可以先檢查這些屬性。


例如,在 .NET Core 2.1的SocketsHttpHandler實(shí)現(xiàn)中,代碼在連接上發(fā)出讀操作,并返回一個(gè)ValueTask<int>實(shí)例。如果該操作同步完成,那么我們不用關(guān)注能否取消該操作。但是,如果它異步完成,在運(yùn)行時(shí)就要發(fā)出取消請(qǐng)求,這樣取消請(qǐng)求會(huì)將連接斷開(kāi)。由于這是一個(gè)非常常用的代碼,并且通過(guò)分析表明這樣做的確有細(xì)微差別,因此代碼的結(jié)構(gòu)基本上如下:


int bytesRead;
{
   ValueTask<int> readTask = _connection.ReadAsync(buffer);
   if (readTask.IsCompletedSuccessfully)
   {
       bytesRead = readTask.Result;
   }
   else
   {
       using (_connection.RegisterCancellation())
       {
           bytesRead = await readTask;
       }
   }
}


這種模式是可以接受的,因?yàn)樵赩alueTask<int>的Result被訪問(wèn)或自身被await之后,不會(huì)再被使用了。


七、新異步API都應(yīng)返回ValueTask/ValueTask<TResult>嗎?


當(dāng)然不是,Task/Task<TResult>仍然是默認(rèn)選擇


正如上文所強(qiáng)調(diào)的那樣,Task/Task<TResult>比ValueTask/ValueTask<TResult>更加容易正確使用,所以除非對(duì)性能的影響大于可用性的影響,否則Task/Task<TResult>仍然是最優(yōu)的。


此外,返回ValueTask<TResult>會(huì)比返回Task<TResult>多一些小開(kāi)銷(xiāo),例如,await Task<TResult>比await ValueTask<TResult>會(huì)更快一些,所以如果你可以使用緩存的Task實(shí)例(例如,你的API返回Task或Task<bool>),你或許應(yīng)該為了更好地性能而仍使用Task和Task<bool>。


而且,ValueTask/ValueTask<TResult>相比Task/Task<TResult>有更多的字段,所以當(dāng)它們被await、并將它們的字段存儲(chǔ)在調(diào)用異步方法的狀態(tài)機(jī)中時(shí),它們會(huì)在該狀態(tài)機(jī)對(duì)象中占用更多的空間。


但是,如果是以下情況,那你應(yīng)該使用ValueTask/ValueTask<TResult>:


1、你希望API的調(diào)用者只能直接await它


2、避免額外的內(nèi)存分配的開(kāi)銷(xiāo)對(duì)API很重要


3、你預(yù)期該API常常是同步完成的,或者在異步完成時(shí)你可以有效地池化對(duì)象。


在添加抽象、虛擬或接口方法時(shí),您還需要考慮這些方法的重載/實(shí)現(xiàn)是否存在這些情況。


八、ValueTask和ValueTask<TResult>的下一步是什么?


對(duì)于.NET Core庫(kù),我們將依然會(huì)看到新的API被添加進(jìn)來(lái),其返回值是Task/Task<TResult>,但在適當(dāng)?shù)牡胤剑覀円矊⒖吹教砑恿诵碌囊訴alueTask/ValueTask<TResult>為返回值的API。


ValueTask/ValueTask<TResult>的一個(gè)關(guān)鍵例子就是在.NET Core 3.0添加新的IAsyncEnumerator<T>支持。IEnumerator<T>公開(kāi)了一個(gè)返回bool的MoveNext方法,異步IAsyncEnumerator<T>則會(huì)公開(kāi)一個(gè)MoveNextAsync方法。


剛開(kāi)始設(shè)計(jì)此功能時(shí),我們認(rèn)為MoveNextAsync應(yīng)返回Task<bool>,一般情況下,通過(guò)緩存的Task<bool>在同步完成時(shí)可以非常高效地執(zhí)行此操作。


但是,考慮到我們期望的異步枚舉的廣泛性,并且考慮到它們基于是基于接口的,其可能有許多不同的實(shí)現(xiàn)方式(其中一些可能會(huì)非常關(guān)注性能和內(nèi)存分配),并且鑒于絕大多數(shù)的消費(fèi)者將通過(guò)await foreach來(lái)使用,我們決定MoveNextAsync返回ValueTask<bool>。


這樣既可以使同步完成案例變得很快,又可以使用可重用的對(duì)象來(lái)使異步完成案例的內(nèi)存分配也減少。


實(shí)際上,在實(shí)現(xiàn)異步迭代器時(shí),C#編譯器會(huì)利用此優(yōu)勢(shì),以使異步迭代器盡可能免于額外內(nèi)存分配。


英文:https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/


- EOF -


該文章在 2024/5/7 11:17:34 編輯過(guò)
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車(chē)隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開(kāi)發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷(xiāo)售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved