ValueTask y ValueTask<TResult> tienen un método Preserve() que se resume como "Obtiene una ValueTask que se puede usar en cualquier momento en el futuro".
¿Qué significa eso y cuándo debemos usarlo? ¿Significa que una ValueTask 'normal' no se puede usar "en ningún momento en el futuro"? Si es así, ¿por qué?
La documentación no es muy clara. De la fuente :
/// <summary>null if representing a successful synchronous completion, /// otherwise a <see cref="Task"/> or a <see cref="IValueTaskSource"/>.</summary> internal readonly object? _obj; ValueTask declara internamente un objeto contenedor anulable para contener la Task o IValueTaskSource que ValueTask encapsula en caso de que sea un ValueTask que lo haga. De lo contrario, es null si ValueTask representa una finalización.
public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask()); Preserve() devuelve una nueva ValueTask representa su tarea subyacente en el caso de que ValueTask represente una Task o un IValueTaskSource (es decir: _obj != null ) o simplemente se devuelve a sí misma si ValueTask representa una finalización síncrona exitosa. Es necesario porque _obj es interno y no se puede probar externamente.
ValueTask es una optimización de rendimiento sobre Task s, pero este rendimiento tiene un costo: no puede usar ValueTask tan libremente como Task . La documentación menciona estas restricciones:
Las siguientes operaciones nunca deben realizarse en una instancia de
ValueTask:
- Esperando la instancia varias veces.
- Llamando
AsTaskvarias veces.- Usar más de una de estas técnicas para consumir la instancia.
Estas restricciones se aplican solo a las ValueTask respaldadas por un IValueTaskSource . Una ValueTask también puede estar respaldada por una Task o por un valor de TResult (para una ValueTask<TResult> ). Si sabe con 100 % de certeza que una ValueTask no está respaldada por un IValueTaskSource , puede usarla con la misma libertad que una Task . Por ejemplo, puede await varias veces, o puede esperar sincrónicamente para que se complete con .GetAwaiter().GetResult() .
En general, no sabe cómo se implementa internamente una ValueTask , e incluso si lo sabe (al estudiar el código fuente), es posible que no desee confiar en un detalle de implementación. Con el método ValueTask.Preserve , puede crear una nueva ValueTask que represente la ValueTask original y se puede usar sin restricciones porque no está creada por un IValueTaskSource . Este método afecta a la ValueTask original solo si está respaldada por un IValueTaskSource ; de lo contrario, no funciona (simplemente devuelve la tarea original sin cambios).
ValueTask preserved = originalValueTask.Preserve(); Después de llamar a Preserve , se ha consumido la ValueTask original. Ya no debe esperarse, Preserve de nuevo ni convertirse en una Task con el método AsTask . Es probable que realizar cualquiera de esas acciones genere una InvalidOperationException . Pero ahora tiene la representación preserved de la misma, que se puede usar con la misma libertad que una Task .
Mi respuesta incluía inicialmente un ejemplo práctico del uso del método Preserve , que resultó ser incorrecto. Este ejemplo se puede encontrar en la cuarta revisión de esta respuesta.