ValueTask<TResult> был введен в .NET Core 2.0 как структура, способная оборачивать TResult или Task<TResult>. Это означает, что объекты этого типа могут быть возвращены из async-метода. Первый плюс от введение этого типа виден сразу: если метод завершился успешно и синхронно, нет необходимости создавать что-либо в куче, достаточно просто для возврата создать экземпляр ValueTask<TResult> со значением результата. Только если метод завершается асинхронно, нам необходимо создать Task<TResult>. В таком случае ValueTask<TResult> используется как обертка над Task<TResult>. Решение сделать ValueTask<TResult> способным агрегировать Task<TResult> было принято с целью оптимизации: и в случае успеха, и в случае неудачи, асинхронный метод создает Task<TResult>, с точки зрения оптимизации по памяти, лучше агрегировать сам объект Task<TResult>, чем держать дополнительные поля в ValueTask<TResult> на разные случаи завершения (например для хранения исключения).
Учитывая вышеизложенное, больше нет необходимости в кэшировании в таких методах как приведенный выше MemoryStream.ReadAsync, вместо этого его можно реализовывать следующим образом: