С введением async / await, поскольку все ваши задачи чтения, кроме одной, являются асинхронными, вы сможете обрабатывать одни и те же данные дважды, используя только один поток ОС.
Я думаю, что вы хотите, это связанный список блоков данных, которые вы видели до сих пор. Затем вы можете иметь несколько пользовательских экземпляров Stream, которые содержат указатель на этот список. Когда блоки попадают в конец списка, они будут собирать мусор. Немедленное повторное использование памяти потребовало бы некоторого другого вида циклического списка и подсчета ссылок. Выполнимо, но сложнее.
Когда ваш пользовательский поток может ответить на вызов ReadAsync из кэша, скопируйте данные, переместите указатель вниз по списку и вернитесь.
Когда ваш поток достиг конца списка кэша, вы хотите выполнить один ReadAsync для базового потока, не ожидая его, и кэшировать возвращенную задачу с помощью блока данных. Поэтому, если какой-либо другой считыватель Stream также догоняет и пытается прочитать больше до завершения этого чтения, вы можете вернуть тот же объект Task.
Таким образом, оба читателя подключат свое ожидание к результату одного и того же вызова ReadAsync. Когда возвращается одно чтение, обе задачи чтения будут последовательно выполнять следующий шаг своего процесса.