L'uso di classi Task o Task ha un collo di bottiglia nelle prestazioni che non abbiamo menzionato negli articoli precedenti. In breve, queste classi guidano quando i risultati sono immediatamente disponibiliAllocazione non necessaria。 Ciò significa che un nuovo Task o un nuovo oggetto Task verrà sempre creato anche se il risultato è già disponibile. Ora, abbiamo menzionato che il concetto di async/await che abbiamo usato negli articoli precedenti esiste fin dal rilascio di .NET 4.5. Questa funzione è stata migliorata da C#7 con la versione .NET 4.7 con la struttura ValueTask che può essere utilizzata come ritorno per funzioni asincrone.
Struttura di ValueTask
La struttura ValueTask è apparsa per la prima volta nel repository corefxlab nel 2015. Questo repository viene utilizzato per sperimentare ed esplorare nuove idee che potrebbero o meno arrivare nel repository principale corefx. Il repository Corefx è il repository dove si trovano tutte le librerie base di .NET Core. È stato sviluppato e suggerito da Stephen Taub per la libreria System.Threading.Tasks.Channels. In quel momento, Stephen fornì una breve spiegazione:
Indirizzo della biblioteca CorefxLab:Il login del link ipertestuale è visibile.
Un ValueTask è una distinta unione di un T e di un Task, che permette a ReadAsync di allocare liberamente per restituire sincronamente i suoi valori T disponibili (a differenza di Task.FromResult, che richiede l'allocazione di un'istanza Task). ValueTask è attesibile, quindi il consumo della maggior parte delle istanze è indistinguibile dal consumo di task. Molte persone vedono i vantaggi nell'usare questa struttura, inclusa in C# 7 come parte del pacchetto NuGet System.Threading.Tasks.Extensions. Quindi, prima di immergerci nella struttura di ValueTask, esaminiamo il problema che utilizza per risolvere. Poiché Task(Task) è un tipo di riferimento, si inizia conIl metodo asincrono che restituisce l'oggetto Task significa che viene allocato sull'heap ogni volta。 Questo è necessario in molti casi.
Tuttavia, in alcuni casi, i metodi asincroni restituiscono risultati immediatamente o completati in modo sincrono. In questi casi, questa allocazione è non necessaria e può diventare costosa nelle parti critiche per le prestazioni del codice. Fino al rilascio di .NET 4.7, non c'era modo di evitarlo, poiché i metodi asincroni dovevano restituire Task, Task <T>o void (quest'ultimo era solitamente indesiderabile). In questa versione di .NET, questo è esteso, il che significa che un metodo asincrono può restituire qualsiasi tipo purché abbia un metodo GetAwaiter accessibile. ValueTask è un esempio concreto di questo tipo, ed è stato aggiunto anche a questa versione.
Puoi consultare il repository corefx e vedere l'implementazione completa di ValueTask, ecco la sezione API che ci interessa:
Come struttura, ValueTask permette di scrivere metodi asincroni che non allocano memoria durante l'esecuzione sincrona. La coerenza API del concetto async/await non è compromessa in questo modo. Inoltre, questa struttura è autonoma, rendendola facile da usare. Ad esempio, se eseguiamo questo codice semplice:
Nel metodo MultiplyAsync, stiamo simulando una situazione in cui vogliamo evitare di usare Task e restituire solo un semplice intero. Questo avviene nell'istruzione if del metodo, dove fondamentalmente controlliamo se il parametro passato è zero. Il problema èAnche se la nostra condizione nell'istruzione if è vera, il codice sopra crea un oggetto Task。 Risolviamo questo problema così:
ValueTask e Task
Come detto prima, ci sono due principali vantaggi nell'utilizzo di ValueTask:
- Miglioramenti delle prestazioni
- Aumentare la flessibilità dell'implementazione
Quindi, quali sono i numeri dietro i miglioramenti delle prestazioni? Osserva questo codice:
Se eseguiamo questo codice, ci vogliono 120ns per eseguire il JIT. Ora, se sostituiamo Task con ValueTask così:
Con JIT, otterremo un tempo di esecuzione di 65ns. Ora, è vero che a causa di Task.Delay non stiamo eseguendo in modo sincrono, ma vediamo un miglioramento nei tempi di esecuzione.
Un altro vantaggio che abbiamo menzionato è la maggiore flessibilità nell'implementazione. Ora, cosa significa esattamente questo? Ebbene, le implementazioni di interfacce asincrone che dovrebbero essere sincronizzate saranno costrette a usare Task.Run o Task.FromResult. Ovviamente, questo porta ai problemi di prestazioni di cui abbiamo parlato prima. Quando usiamo ValueTask, è più probabile che scegliamo tra implementazioni sincrone o asincrone. Tieni presente che questo potrebbe essere un segno che il tuo codice potrebbe non essere ben progettato se succede a te.
Ad esempio, si osserva questa interfaccia:
Supponiamo che tu voglia chiamarlo da un codice così:
Poiché usiamo ValueTask nell'interfaccia, l'implementazione dell'interfaccia può essere sincrona o asincrona. Possiamo ottenere questo vantaggio saltando praticamente l'aggiunta di alcune funzioni a IT che gestiscono il comportamento di sincronizzazione. È molto più facile usare questa interfaccia in questo modo. Ecco un'implementazione sincrona dell'interfaccia sopra:
Ecco un'implementazione asincrona della stessa interfaccia:
Tuttavia, dobbiamo considerare alcuni compromessi prima di usare ValueTask. È facile pensare che ValueTask dovrebbe essere usato di default invece di Task, cosa che certamente non è vera. Ad esempio, sebbene ValueTask ci aiuti a evitare assegnazioni inutili quando è disponibile la sincronizzazione dei risultati, contiene anche due campi.
È importante ricordare che questa è la struttura che stiamo usando qui, il che significa che usiamo i tipi di valore e tutti i loro oneri. Task, invece, è un tipo di riferimento con un solo campo.Quando usi ValueTask, abbiamo più dati da elaborare e elaborare. Se un tale metodo è atteso in un metodo asincrono, allora il metodo asincrono èAnche la macchina degli stati sarà più grande, perché memorizzare l'intera struttura di solito richiede più spazio rispetto a memorizzare un singolo riferimento.
Ecco perché i team di Microsoft consigliano effettivamente di usare Task o Task come tipo di ritorno predefinito per i metodi asincroni. Solo dopo l'analisi delle prestazioni dovresti considerare di passare a ValueTask.
sommario
ValueTask è una struttura introdotta in .NET 4.7 che ci offre molte possibilità di utilizzare metodi asincroni in .NET. Tuttavia, non è privo di un prezzo. Questo è utile per metodi critici per le prestazioni eseguiti in modo sincrono. Con loro possiamo evitare di assegnare oggetti inutili. Tuttavia, come tipo di valore, comporta tutti i problemi che di solito hanno i tipi di valore. Pertanto, possiamo trarre beneficio da questa struttura, ma dobbiamo essere cauti.
Indirizzo originale:Il login del link ipertestuale è visibile.
|