Att använda Task eller Task-klasser har en prestandaflaskhals som vi inte nämnt i tidigare artiklar. Kort sagt, dessa klasser leder när resultaten är tillgängliga omedelbartOnödig fördelning。 Detta innebär att en ny Task eller Task-objekt alltid kommer att skapas även om resultatet redan är tillgängligt. Nu nämnde vi att async/await-konceptet vi använde i tidigare artiklar har funnits sedan .NET 4.5-släppet. Denna funktion har förbättrats sedan C# 7 med .NET 4.7-versionen med ValueTask-strukturen som kan användas som retur för asynkrona funktioner.
ValueTask-struktur
ValueTask-strukturen dök först upp i corefxlab-arkivet 2015. Detta arkiv används för att experimentera och utforska nya idéer som kanske eller kanske inte når huvud-corefx-arkivet. Corefx-arkivet är det arkiv där alla .NET Core-basbibliotek finns. Den utvecklades och föreslogs av Stephen Taub för biblioteket System.Threading.Tasks.Channels. Vid den tiden gav Stephen en kort förklaring:
CoreFXLab-biblioteksadress:Inloggningen med hyperlänken är synlig.
En ValueTask är en distinkt union av ett T och en Task, vilket gör att ReadAsync fritt kan allokera för att synkront returnera sina tillgängliga T-värden (till skillnad från att använda Task.FromResult, som kräver en allokering av en Task-instans). ValueTask är väntbar, så konsumtionen av de flesta instanser är omöjlig att skilja från konsumtionen av uppgifter. Många ser fördelarna med att använda denna struktur, som ingår i C# 7 som en del av System.Threading.Tasks.Extensions NuGet-paketet. Så, innan vi dyker ner i ValueTask-strukturen, låt oss undersöka problemet den använder för att lösa. Eftersom Task(Task) är en referenstyp, börja medDen asynkrona metoden som returnerar Task-objektet innebär att det tilldelas heapen varje gång。 Detta är nödvändigt i många fall.
I vissa fall returnerar dock asynkrona metoder resultat omedelbart eller slutförs synkront. I dessa fall är denna allokering onödig och kan bli kostsam i prestandakritiska delar av koden. Fram till .NET 4.7-släppet fanns det inget sätt att undvika detta, eftersom asynkrona metoder var tvungna att returnera Task, Task <T>eller void (den sista var vanligtvis oönskad). I denna version av .NET är detta utökat, vilket innebär att en asynkron metod kan returnera vilken typ som helst så länge den har en tillgänglig GetAwaiter-metod. ValueTask är ett konkret exempel på denna typ, och det lades också till i denna version.
Du kan bläddra i corefx-arkivet och se hela implementeringen av ValueTask, här är API-avsnittet vi är intresserade av:
Som struktur tillåter ValueTask att skriva asynkrona metoder som inte allokerar minne under synkron körtid. API-konsistensen i async/await-konceptet kompromissas inte på detta sätt. Dessutom väntar denna struktur på sig själv, vilket gör den lätt att använda. Till exempel, om vi kör denna enkla kod:
I MultiplyAsync-metoden simulerar vi en situation där vi vill undvika att använda Task och bara returnera ett enkelt heltal. Detta görs i metodens if-sats, där vi i princip kontrollerar om den passerade parametern är noll. Problemet ärÄven om vårt villkor i if-satsen är sant, skapar koden ovan ett Task-objekt。 Vi löser detta problem så här:
ValueTask och Task
Som nämnts tidigare finns det två huvudsakliga fördelar med att använda ValueTask:
- Prestandaförbättringar
- Öka implementeringsflexibiliteten
Så, vilka siffror ligger bakom prestandaförbättringarna? Observera denna kod:
Om vi kör den här koden krävs det 120 ns för att köra JIT:en. Nu, om vi ersätter Task med ValueTask så här:
Med JIT får vi en genomförandetid på 65 ns. Nu är det sant att på grund av Task.Delay kör vi inte synkront, men vi ser en förbättring i exekveringstiden.
En annan fördel vi nämnde är den ökade flexibiliteten i genomförandet. Vad betyder det egentligen? Implementeringar av asynkrona gränssnitt som bör synkroniseras kommer att tvingas använda Task.Run eller Task.FromResult. Detta leder förstås till de prestandaproblem vi diskuterade tidigare. När vi använder ValueTask är vi mer benägna att välja mellan synkrona eller asynkrona implementationer. Tänk på att detta kan vara ett tecken på att din kod kanske inte är väl utformad om detta händer dig.
Till exempel, observera detta gränssnitt:
Låt oss säga att du vill kalla det från kod så här:
Eftersom vi använder ValueTask i gränssnittet kan implementeringen av gränssnittet vara synkron eller asynkron. Vi kan få denna fördel genom att i princip hoppa över att lägga till vissa funktioner i IThing som hanterar synkroniseringsbeteende. Det är mycket enklare att använda detta gränssnitt på det sättet. Här är en synkron implementation av ovanstående gränssnitt:
Här är en asynkron implementation av samma gränssnitt:
Vi måste dock överväga vissa avvägningar innan vi använder ValueTask. Det är lätt att tro att ValueTask bör användas som standard istället för Task, vilket definitivt inte är fallet. Till exempel, även om ValueTask hjälper oss att undvika onödiga tilldelningar när resultatsynkronisering är tillgänglig, innehåller det också två fält.
Det är viktigt att komma ihåg att detta är den struktur vi använder här, vilket betyder att vi använder värdetyper och alla deras bördor. Uppgift, å andra sidan, är en referenstyp med endast ett fält.När du använder ValueTask har vi mer data att bearbeta och bearbeta. Om en sådan metod väntas på i en asynkron metod, så är den asynkrona metodenTillståndsmaskinen kommer också att vara större, eftersom lagring av hela strukturen vanligtvis kräver mer utrymme än att lagra en enda referens.
Det är därför Microsoft faktiskt rekommenderar att använda Task eller Task som standardtyp för retur för asynkrona metoder. Först efter prestationsanalys bör du överväga att byta till ValueTask.
sammanfattning
ValueTask är en struktur som introducerades i .NET 4.7 och ger oss många möjligheter att använda asynkrona metoder i .NET. Men det är inte utan pris. Detta är användbart för prestandakritiska metoder som körs synkront. Med dem kan vi undvika att tilldela onödiga objekt. Ändå följer det som värdetyp med alla de problem som värdetyper vanligtvis har. Därför kan vi dra nytta av denna struktur, men vi måste vara försiktiga.
Ursprunglig adress:Inloggningen med hyperlänken är synlig.
|