Server Streaming in gRPC with .NET
1. What is Server Streaming?
In a server streaming call, the client sends one request and the server responds with a stream of multiple messages over time. The client reads messages as they arrive until the server closes the stream.
Think of server streaming like subscribing to a live news ticker. You make one request ("subscribe to news") and then you keep receiving updates one by one as they happen — you do not wait for all news to be collected before reading.
| Call Type | Client Sends | Server Responds |
|---|---|---|
| Unary | 1 message | 1 message |
| Client Streaming | Many messages | 1 message |
| Server Streaming | 1 message | Many messages (stream) |
| Bidirectional Streaming | Many messages | Many messages |
Common use cases:
- Downloading a large dataset in chunks
- Real-time notifications or event feeds
- Progress updates on a long-running job
- Paginating large query results
2. Adding stream to the Proto Response
To declare a server streaming RPC, add the stream keyword before the response type only. The request has no stream keyword.
The stream keyword on the response side tells gRPC: "the server will send multiple HelloReply messages in response to one HelloRequest."
3. Server Implementation
On the server side (GrpcService1/Services/GreeterService.cs), the method signature for a server streaming call is different from a unary call. Instead of returning Task<HelloReply>, it returns just Task and receives an IServerStreamWriter<HelloReply> argument.
Key points about the server method:
- The method is
asyncand returns a plainTask(notTask<T>). - You use
responseStream.WriteAsync()to send each individual message. - Always check
context.CancellationToken.IsCancellationRequestedso you can stop early if the client cancels or the deadline passes. - When the method finishes, gRPC automatically closes the stream and tells the client there are no more messages.
4. Writing to the Stream on the Server
IServerStreamWriter<T> has one primary method for sending messages:
| Method | Returns | Description |
|---|---|---|
WriteAsync(message) | Task | Serializes and sends one message to the client. Always await this. |
WriteOptions | WriteOptions | Controls compression for the next write (optional) |
Important: You cannot call WriteAsync() in parallel. Always await each write before calling it again. gRPC requires writes to be sequential on a stream.
5. Using Deadlines
A deadline is a point in time after which the call should automatically stop. The client sets the deadline when making the call. If the deadline is reached before the server finishes, the client receives a DeadlineExceeded status code.
Think of a deadline like a pizza delivery time guarantee. The restaurant (server) promises to keep sending updates, but if the pizza is not delivered within 30 minutes (the deadline), the order is automatically cancelled.
The server should check the cancellation token and return partial results when the deadline is close:
6. Client Implementation
On the client side (BasicGrpcClient/Program.cs), calling a server streaming method returns an AsyncServerStreamingCall<HelloReply>. You then read the ResponseStream to receive messages one by one.
You can also pass a deadline as a named parameter to limit how long the call can run:
7. Reading the Stream on the Client
Just like the server-side reader, you have two options for reading the response stream:
Option A: ReadAllAsync() — Recommended
Option B: MoveNext() + Current — Manual approach
| Approach | When to use |
|---|---|
ReadAllAsync() + await foreach | Most cases — clean and readable |
MoveNext() + Current | When you need explicit cancellation token or older C# compatibility |
8. The AsyncServerStreamingCall Object
client.GetMultipleReplies() returns an AsyncServerStreamingCall<HelloReply>.
| Property / Method | Type | What it does |
|---|---|---|
.ResponseStream | IAsyncStreamReader<HelloReply> | Read response messages from the server |
.ResponseStream.ReadAllAsync() | IAsyncEnumerable<HelloReply> | Stream all messages via await foreach |
.ResponseStream.MoveNext() | Task<bool> | Advance to next message; returns false when done |
.ResponseStream.Current | HelloReply | The current message (after MoveNext returned true) |
.ResponseHeadersAsync | Task<Metadata> | Headers sent by the server before the first message |
.GetStatus() | Status | gRPC status code — available after stream ends |
9. Full Working Example
GrpcService1/Services/GreeterService.cs
BasicGrpcClient/Program.cs
Expected output (with 5 messages, 500ms apart, within 15-second deadline):
Summary
- Server streaming uses the
streamkeyword before the response type in the proto. - The server method returns
Task(notTask<T>) and receives anIServerStreamWriter<T>. - Use
responseStream.WriteAsync()to send each message — alwaysawaitit before writing again. - Always check
context.CancellationToken.IsCancellationRequestedso the server can stop gracefully on deadline or cancellation. - The client calls the method with an optional
deadlinenamed parameter to set a time limit. - The client receives
AsyncServerStreamingCall<T>and reads messages viaResponseStream.ReadAllAsync()orMoveNext(). - Catch
RpcExceptionwithStatusCode.DeadlineExceededto handle timeouts gracefully.