ValueTask<T>
non è un sottoinsieme di Task<T>
, è un superset .
ValueTask<T>
è un'unione discriminata di T e a Task<T>
, che lo rende privo di allocazione per ReadAsync<T>
restituire in modo sincrono un valore T che ha a disposizione (al contrario dell'uso Task.FromResult<T>
, che deve allocare Task<T>
un'istanza). ValueTask<T>
è attendibile, quindi la maggior parte del consumo di istanze sarà indistinguibile da a Task<T>
.
ValueTask, essendo una struttura, consente di scrivere metodi asincroni che non allocano memoria quando vengono eseguiti in modo sincrono senza compromettere la coerenza dell'API. Immagina di avere un'interfaccia con un metodo di restituzione delle attività. Ogni classe che implementa questa interfaccia deve restituire un'attività anche se si esegue in modo sincrono (si spera utilizzando Task.FromResult). Ovviamente puoi avere 2 metodi diversi sull'interfaccia, uno sincrono e uno asincrono, ma ciò richiede 2 diverse implementazioni per evitare "sync over async" e "async over sync".
Quindi ti consente di scrivere un metodo che è asincrono o sincrono, piuttosto che scrivere un metodo altrimenti identico per ciascuno. Potresti usarlo ovunque tu usi Task<T>
ma spesso non aggiungerebbe nulla.
Bene, aggiunge una cosa: aggiunge una promessa implicita al chiamante che il metodo effettivamente utilizza la funzionalità aggiuntiva che ValueTask<T>
fornisce. Personalmente preferisco scegliere i parametri e restituire i tipi che dicono al chiamante il più possibile. Non restituire IList<T>
se l'enumerazione non è in grado di fornire un conteggio; non tornare IEnumerable<T>
se può. I tuoi consumatori non dovrebbero consultare alcuna documentazione per sapere quali dei tuoi metodi possono ragionevolmente essere chiamati in modo sincrono e quali no.
Non vedo i futuri cambiamenti di design come un argomento convincente lì. Al contrario: se un metodo cambia la sua semantica, dovrebbe interrompere la build fino a quando tutte le chiamate ad esso non vengono aggiornate di conseguenza. Se questo è considerato indesiderabile (e credetemi, sono solidale con il desiderio di non infrangere la build), prendete in considerazione il controllo delle versioni dell'interfaccia.
Questo è essenzialmente ciò che serve per scrivere forte.
Se alcuni programmatori che progettano metodi asincroni nel tuo negozio non sono in grado di prendere decisioni informate, potrebbe essere utile assegnare un mentore senior a ciascuno di quei programmatori meno esperti e avere una revisione settimanale del codice. Se indovinano male, spiega perché dovrebbe essere fatto diversamente. È un problema per i ragazzi più anziani, ma porterà i giovani a velocizzare molto più rapidamente che lanciarli nel profondo e dare loro una regola arbitraria da seguire.
Se l'uomo che ha scritto il metodo non sa se può essere chiamato in modo sincrono, chi sulla Terra lo fa ?!
Se hai così tanti programmatori inesperti che scrivono metodi asincroni, anche quelle stesse persone li chiamano? Sono qualificati per capire da soli quali sono sicuri di chiamare asincroni o inizieranno ad applicare una regola altrettanto arbitraria a come chiamano queste cose?
Il problema qui non sono i tuoi tipi di ritorno, ma i programmatori vengono assegnati a ruoli per i quali non sono pronti. Questo deve essere successo per un motivo, quindi sono sicuro che non può essere banale da risolvere. Descriverlo certamente non è una soluzione. Ma cercare un modo per intrufolarsi oltre il compilatore non è una soluzione.
ValueTask<T>
(in termini di allocazioni) di non materializzarsi per operazioni che sono effettivamente asincrone (perché in quel casoValueTask<T>
avrà ancora bisogno di allocazione di heap). C'è anche la questione diTask<T>
avere molto altro supporto all'interno delle biblioteche.