Understanding PipeReader in .NET


When working with high-performance I/O in .NET, the System.IO
.Pipelines
API is a game changer. At the heart of it lies PipeReader
, a key component that allows you to efficiently read streaming data without unnecessary memory allocations or copying.
In .NET 10, PipeReader
has received a notable upgrade: JsonSerializer.Deserialize
now directly supports it. This removes a common performance bottleneck for developers working with JSON data over pipelines.
What is PipeReader
?
PipeReader
is the read side of a Pipe
in .NET's System.IO
.Pipelines
API. It’s designed for high-throughput, low-allocation reading of data streams.
A Pipe
acts as a bridge between a producer (data source) and a consumer (data processor):
Producer writes to a
PipeWriter
Consumer reads from a
PipeReader
This approach:
Reduces memory copying
Supports async I/O naturally
Allows partial reads without blocking
Example – Reading from a PipeReader:
var pipe = new Pipe();
// Producer
_ = Task.Run(async () =>
{
var writer = pipe.Writer;
byte[] message = Encoding.UTF8.GetBytes("Hello from Pipe!");
writer.Write(message);
await writer.FlushAsync();
writer.Complete();
});
// Consumer
while (true)
{
ReadResult result = await pipe.Reader.ReadAsync();
ReadOnlySequence<byte> buffer = result.Buffer;
foreach (var segment in buffer)
Console.Write(Encoding.UTF8.GetString(segment.Span));
pipe.Reader.AdvanceTo(buffer.End);
if (result.IsCompleted) break;
}
pipe.Reader.Complete();
The Old Problem with PipeReader
and JSON
Before .NET 10, if you wanted to deserialize JSON from a PipeReader
, you couldn’t feed it directly into JsonSerializer
.
Instead, you had to convert it to a Stream
first:
using var stream = pipeReader.AsStream();
var person = await JsonSerializer.DeserializeAsync<Person>(stream);
This extra conversion step:
Added overhead (memory allocations, data copying)
Made the code less clean
What’s New in .NET 10
In .NET 10, JsonSerializer
natively supports PipeReader
for deserialization.
Now you can write:
Person? person = await JsonSerializer.DeserializeAsync<Person>(pipeReader);
No conversion, no intermediate Stream
, just direct pipeline → object mapping.
Benefits of This Change
Performance: Avoids unnecessary copying and allocations
Cleaner code: No boilerplate
AsStream()
callsBetter integration: Works naturally with pipeline-based networking code, like
Sockets
Real-World Example – Deserializing JSON from a Socket
var pipe = new Pipe();
_ = FillPipeFromSocketAsync(socket, pipe.Writer);
Person? person = await JsonSerializer.DeserializeAsync<Person>(pipe.Reader);
Console.WriteLine($"Hello {person?.Name}!");
Here, FillPipeFromSocketAsync
reads from the socket directly into the pipe, and the JsonSerializer
processes the stream of bytes as they arrive.
Conclusion
PipeReader
has always been a powerful tool for building high-performance data processing in .NET. With .NET 10’s new direct JsonSerializer
support, working with JSON in pipelines becomes simpler and faster.
If you’re already using System.IO
.Pipelines
in networking or file processing, this upgrade can help you eliminate unnecessary steps and gain a performance boost.
I’m Morteza Jangjoo and “Explaining things I wish someone had explained to me”
Tags: .NET
, CSharp
, Pipelines
, JsonSerializer
, PipeReader
, Performance
Subscribe to my newsletter
Read articles from Morteza Jangjoo directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
