A partir de C # 7.0, los métodos asíncronos pueden devolver ValueTask <T>. La explicación dice que debería usarse cuando tenemos un resultado almacenado en caché o simulando asíncrono a través de código sincrónico. Sin embargo, todavía no entiendo cuál es el problema con el uso de ValueTask siempre o de hecho por qué async / await no se creó con un tipo de valor desde el principio. ¿Cuándo ValueTask no podría hacer el trabajo?
c#
asynchronous
Stilgar
fuente
fuente
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.Respuestas:
De los documentos de la API (énfasis agregado):
fuente
Task
asignación (que es pequeña y barata en estos días), pero a costa de aumentar la asignación existente de la persona que llama y duplicar el tamaño del valor de retorno (afectando la asignación del registro). Si bien es una opción clara para un escenario de lectura almacenado, aplicarlo de manera predeterminada a todas las interfaces no es algo que recomendaría.Task
oValueTask
puede usarse como un tipo de retorno síncrono (conTask.FromResult
). Pero todavía hay valor (je)ValueTask
si tienes algo que esperas que sea sincrónico.ReadByteAsync
siendo un ejemplo clásico Creo queValueTask
fue creado principalmente para los nuevos "canales" (flujos de bytes de bajo nivel), posiblemente también utilizados en el núcleo ASP.NET donde el rendimiento realmente importa.Task<T>
por defecto. Esto es solo porque la mayoría de los desarrolladores no están familiarizados con las restriccionesValueTask<T>
(específicamente, la regla de "solo consumir una vez" y la regla de "no bloquear"). Dicho esto, si todos los desarrolladores de tu equipo son buenosValueTask<T>
, entonces recomendaría una pauta de preferencia a nivel de equipoValueTask<T>
.Los tipos de estructura no son gratuitos. Copiar estructuras que son más grandes que el tamaño de una referencia puede ser más lento que copiar una referencia. Almacenar estructuras que son más grandes que una referencia requiere más memoria que almacenar una referencia. Es posible que las estructuras que tienen más de 64 bits no se registren cuando se puede registrar una referencia. Los beneficios de una menor presión de recolección no pueden exceder los costos.
Los problemas de rendimiento deben abordarse con una disciplina de ingeniería. Establezca objetivos, mida su progreso frente a los objetivos y luego decida cómo modificar el programa si no se cumplen los objetivos, midiendo en el camino para asegurarse de que sus cambios sean realmente mejoras.
await
se agregó a C # mucho después de que elTask<T>
tipo ya existiera. Hubiera sido algo perverso inventar un nuevo tipo cuando ya existía. Yawait
pasó por muchas iteraciones de diseño antes de decidirse por el que se envió en 2012. Lo perfecto es enemigo de lo bueno; es mejor enviar una solución que funcione bien con la infraestructura existente y luego, si hay una demanda del usuario, proporcionar mejoras más adelante.También noto que la nueva característica de permitir que los tipos suministrados por el usuario sean la salida de un método generado por el compilador agrega un riesgo considerable y una carga de prueba. Cuando las únicas cosas que puede devolver son nulas o una tarea, el equipo de prueba no tiene que considerar ningún escenario en el que se devuelva algún tipo absolutamente loco. Probar un compilador significa descubrir no solo qué programas es probable que escriban las personas, sino qué programas son posibles de escribir, porque queremos que el compilador compile todos los programas legales, no solo todos los programas sensibles. Eso es caro.
El propósito de la cosa es mejorar el rendimiento. No hace el trabajo si no mejora de manera medible y significativa el rendimiento. No hay garantía de que lo haga.
fuente
ValueTask<T>
no es un subconjunto deTask<T>
, es un superconjunto .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 regreseIList<T>
si la enumeración no puede proporcionar un conteo; no regreseIEnumerable<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.
fuente
ValueTask<T>
, supongo que el tipo que escribió el método lo hizo porque el método realmente utiliza la funcionalidad queValueTask<T>
agrega. No entiendo por qué crees que es deseable que todos tus métodos tengan el mismo tipo de retorno. ¿Cuál es el objetivo allí?object
porque tal vez algún día querrá que regresen enint
lugar de hacerlostring
.Hay algunos cambios en .Net Core 2.1 . A partir de .net core 2.1 ValueTask puede representar no solo las acciones sincrónicas completadas sino también la asincrónica completada. Además recibimos tipo no genérico
ValueTask
.Dejaré el comentario de Stephen Toub relacionado con su pregunta:
La función se puede usar no solo en .net core 2.1. Podrá usarlo con el paquete System.Threading.Tasks.Extensions .
fuente