Skip to content

Support Streaming in .NET#3403

Open
xiazcy wants to merge 1 commit intomasterfrom
net-streaming
Open

Support Streaming in .NET#3403
xiazcy wants to merge 1 commit intomasterfrom
net-streaming

Conversation

@xiazcy
Copy link
Copy Markdown
Contributor

@xiazcy xiazcy commented Apr 30, 2026

Updated .NET driver to support streaming HTTP responses. Changes include:

Non-breaking:

  • Connection.SubmitAsync uses ResponseHeadersRead + background Task + Channel<object> to stream results incrementally
  • ResponseMessageSerializer yields results via IAsyncEnumerable<object> instead of buffering into List<object>
  • StreamExtensions reads use ReadExactlyAsync for EOF safety, reads and writes use BinaryPrimitives
  • Traversal API (Next(), ToList(), HasNext(), etc.) unchanged, blocks internally via WaitUnwrap()

Breaking:

  • ResultSet<T> rebuilt as IAsyncEnumerable<T> (was IReadOnlyCollection<T>). Count and sync foreach removed, use ToListAsync() or await foreach
  • IMessageSerializer.DeserializeMessageAsync accepts StreamIAsyncEnumerable<object> (was byte[]Task<ResponseMessage<List<object>>>). Custom serializers must update
  • ResponseMessage<T> deleted
  • ResponseException thrown during result iteration, not during SubmitAsync, for direct ResultSet<T> consumers
  • ITraversal.Traversers changed from IEnumerable<Traverser> to IAsyncEnumerable<Traverser>; IEnumerator inheritance removed (Reset() gone)
  • Minimum runtime: .NET 8.0 (was netstandard2.0;net6.0)

VOTE +1

private readonly ChannelReader<object> _channelReader;
private readonly CancellationTokenSource _disposeCts;
private readonly Task _backgroundTask;
private int _enumerated; // 0 = not yet, 1 = already enumerated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: not immediately obvious that these are ints not booleans due to the use of InterLocked and restrictions prior to .NET 9.

public async Task<ResponseMessage<List<object>>> ReadValueAsync(MemoryStream stream,
GraphBinaryReader reader, CancellationToken cancellationToken = default)
/// <returns>An async enumerable of deserialized result objects.</returns>
public async IAsyncEnumerable<object> ReadStreamingAsync(Stream stream,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is the ResponseMessageSerializer, but it no longer returns a ResponseMessage. This probably require some restructuring. Is ResponseMessage needed at all anymore?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, ResponseMessage was deleted. I'll update naming & stuff for this one.

/// <param name="cancellationToken">The token to cancel the operation. The default value is None.</param>
/// <returns>The read <see cref="byte"/>.</returns>
public static async Task<byte> ReadByteAsync(this Stream stream, CancellationToken cancellationToken = default)
public static async ValueTask<byte> ReadByteAsync(this Stream stream,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: These types of changes exist throughout the PR that are just spacing changes, but the make the diff hard to read. Can these be reverted?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the change from Task to ValueTask is a valid change but was the line break necessary?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants