ValueTask<T>
no es un subconjunto de Task<T>
, es un superconjunto .
ValueTask<T>
es una unión discriminada de T y a Task<T>
, lo que lo hace libre de asignación para ReadAsync<T>
devolver sincrónicamente un valor T que tiene disponible (en contraste con el uso Task.FromResult<T>
, que necesita asignar una Task<T>
instancia). ValueTask<T>
es esperable, por lo que la mayoría del consumo de instancias será indistinguible de a Task<T>
.
ValueTask, al ser una estructura, permite escribir métodos asincrónicos que no asignan memoria cuando se ejecutan sincrónicamente sin comprometer la consistencia de la API. Imagine tener una interfaz con un método de devolución de tareas. Cada clase que implemente esta interfaz debe devolver una Tarea incluso si se ejecuta sincrónicamente (con suerte usando Task.FromResult). Por supuesto, puede tener 2 métodos diferentes en la interfaz, uno sincrónico y uno asíncrono, pero esto requiere 2 implementaciones diferentes para evitar "sincronizar sobre sincronización" y "sincronizar sobre sincronización".
Por lo tanto, le permite escribir un método que sea asíncrono o sincrónico, en lugar de escribir un método idéntico para cada uno. Puede usarlo en cualquier lugar que use, Task<T>
pero a menudo no agregaría nada.
Bueno, agrega una cosa: agrega una promesa implícita a la persona que llama de que el método realmente utiliza la funcionalidad adicional que ValueTask<T>
proporciona. Personalmente, prefiero elegir los parámetros y los tipos de retorno que le dicen a la persona que llama tanto como sea posible. No regrese IList<T>
si la enumeración no puede proporcionar un conteo; no regrese IEnumerable<T>
si puede. Sus consumidores no deberían tener que buscar ninguna documentación para saber cuáles de sus métodos se pueden llamar razonablemente sincrónicamente y cuáles no.
No veo cambios futuros en el diseño como un argumento convincente allí. Todo lo contrario: si un método cambia su semántica, debería romper la compilación hasta que todas las llamadas a él se actualicen en consecuencia. Si eso se considera indeseable (y créanme, simpatizo con el deseo de no romper la compilación), considere la versión de la interfaz.
Esto es esencialmente para lo que sirve la escritura fuerte.
Si algunos de los programadores que diseñan métodos asíncronos en su tienda no pueden tomar decisiones informadas, puede ser útil asignar un mentor principal a cada uno de esos programadores menos experimentados y realizar una revisión semanal del código. Si adivinan mal, explique por qué debe hacerse de manera diferente. Es algo elevado para los muchachos mayores, pero hará que los juniors aceleren mucho más rápido que simplemente lanzarlos al fondo y darles una regla arbitraria a seguir.
Si el tipo que escribió el método no sabe si se puede llamar sincrónicamente, ¿ quién demonios lo sabe?
Si tienes tantos programadores inexpertos que escriben métodos asincrónicos, ¿los llaman también esas mismas personas? ¿Están calificados para descubrir por sí mismos cuáles son seguros para llamar asíncronos, o comenzarán a aplicar una regla arbitraria similar a cómo llaman a estas cosas?
El problema aquí no son sus tipos de retorno, sino que los programadores tienen roles para los que no están preparados. Eso debe haber sucedido por una razón, así que estoy seguro de que no puede ser trivial solucionarlo. Describirlo ciertamente no es una solución. Pero buscar una manera de escabullir el problema más allá del compilador tampoco es una solución.
ValueTask<T>
(en términos de asignaciones) no materializarse para operaciones que en realidad son asíncronas (porque en ese casoValueTask<T>
aún necesitará una asignación de montón). También está la cuestión deTask<T>
tener mucho otro soporte dentro de las bibliotecas.