-
Notifications
You must be signed in to change notification settings - Fork 5.1k
System.Text.Json: Add IAsyncEnumerable support #50778
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
3e9c0fc
implement IAsyncEnumerable JsonConverter
eiriktsarpalis b758985
Prototype of IAsyncEnumerable deserialize with Stream
steveharter 2dab454
Use a Queue + test buffersizes
steveharter d00415d
Avoid 1 item lag
steveharter e497c82
Add support for Serialize
steveharter 0ba7172
Misc cleanup on test
steveharter 4d69755
extend DeserializeAsyncEnumerable test coverage
eiriktsarpalis 8ce7722
Update src/libraries/System.Text.Json/src/System/Text/Json/Serializat…
eiriktsarpalis 4a88d64
address feedback
eiriktsarpalis 3eec24c
tweak test buffer values
eiriktsarpalis 917d630
Update src/libraries/System.Text.Json/src/System/Text/Json/Serializat…
eiriktsarpalis ebdce43
Update src/libraries/System.Text.Json/src/System/Text/Json/Serializat…
eiriktsarpalis ae83e8c
address feedback
eiriktsarpalis 102ed44
increase delayInterval in serialization tests
eiriktsarpalis 197fdce
address feedback
eiriktsarpalis 8ee1051
address feedback
eiriktsarpalis 9a03fed
add test on exceptional IAsyncDisposable disposal
eiriktsarpalis 537dc54
address feedback
eiriktsarpalis c5f57b4
Update src/libraries/System.Text.Json/src/System/Text/Json/Serializat…
eiriktsarpalis 68f71c7
Update src/libraries/System.Text.Json/src/System/Text/Json/Serializat…
eiriktsarpalis bfc0e7b
fix build and remove dead code
eiriktsarpalis 65b2ec4
address feedback
eiriktsarpalis 9ccf772
Revert unneeded JsonClassInfo.ElementType workaround
eiriktsarpalis ac56fb3
remove state allocation on async deserialization methods
eiriktsarpalis d893083
remove tooling artifacts
eiriktsarpalis 2a5b5f1
address feedback
eiriktsarpalis 77fc902
reset AsyncEnumeratorIsPendingCompletion field
eiriktsarpalis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
.../System/Text/Json/Serialization/Converters/Collection/IAsyncEnumerableConverterFactory.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Text.Json.Serialization.Converters; | ||
|
||
namespace System.Text.Json.Serialization | ||
{ | ||
/// <summary> | ||
/// Converter for streaming <see cref="IAsyncEnumerable{T}" /> values. | ||
/// </summary> | ||
internal sealed class IAsyncEnumerableConverterFactory : JsonConverterFactory | ||
{ | ||
public override bool CanConvert(Type typeToConvert) => GetAsyncEnumerableInterface(typeToConvert) is not null; | ||
eiriktsarpalis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) | ||
{ | ||
Type? asyncEnumerableInterface = GetAsyncEnumerableInterface(typeToConvert); | ||
Debug.Assert(asyncEnumerableInterface is not null, $"{typeToConvert} not supported by converter."); | ||
|
||
Type elementType = asyncEnumerableInterface.GetGenericArguments()[0]; | ||
Type converterType = typeof(IAsyncEnumerableOfTConverter<,>).MakeGenericType(typeToConvert, elementType); | ||
return (JsonConverter)Activator.CreateInstance(converterType)!; | ||
} | ||
|
||
private static Type? GetAsyncEnumerableInterface(Type type) | ||
=> IEnumerableConverterFactoryHelpers.GetCompatibleGenericInterface(type, typeof(IAsyncEnumerable<>)); | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
.../src/System/Text/Json/Serialization/Converters/Collection/IAsyncEnumerableOfTConverter.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace System.Text.Json.Serialization.Converters | ||
{ | ||
internal sealed class IAsyncEnumerableOfTConverter<TAsyncEnumerable, TElement> | ||
: IEnumerableDefaultConverter<TAsyncEnumerable, TElement> | ||
where TAsyncEnumerable : IAsyncEnumerable<TElement> | ||
{ | ||
internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out TAsyncEnumerable value) | ||
{ | ||
if (!typeToConvert.IsAssignableFrom(typeof(IAsyncEnumerable<TElement>))) | ||
{ | ||
ThrowHelper.ThrowNotSupportedException_CannotPopulateCollection(TypeToConvert, ref reader, ref state); | ||
} | ||
|
||
return base.OnTryRead(ref reader, typeToConvert, options, ref state, out value!); | ||
} | ||
|
||
protected override void Add(in TElement value, ref ReadStack state) | ||
{ | ||
((BufferedAsyncEnumerable)state.Current.ReturnValue!)._buffer.Add(value); | ||
} | ||
|
||
protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options) | ||
{ | ||
state.Current.ReturnValue = new BufferedAsyncEnumerable(); | ||
} | ||
|
||
internal override bool OnTryWrite(Utf8JsonWriter writer, TAsyncEnumerable value, JsonSerializerOptions options, ref WriteStack state) | ||
{ | ||
if (!state.SupportContinuation) | ||
{ | ||
ThrowHelper.ThrowNotSupportedException_TypeRequiresAsyncSerialization(TypeToConvert); | ||
} | ||
|
||
return base.OnTryWrite(writer, value, options, ref state); | ||
} | ||
|
||
[Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2012:Use ValueTasks correctly", Justification = "Converter needs to consume ValueTask's in a non-async context")] | ||
protected override bool OnWriteResume(Utf8JsonWriter writer, TAsyncEnumerable value, JsonSerializerOptions options, ref WriteStack state) | ||
{ | ||
IAsyncEnumerator<TElement> enumerator; | ||
ValueTask<bool> moveNextTask; | ||
|
||
if (state.Current.AsyncEnumerator is null) | ||
{ | ||
enumerator = value.GetAsyncEnumerator(state.CancellationToken); | ||
moveNextTask = enumerator.MoveNextAsync(); | ||
// we always need to attach the enumerator to the stack | ||
// since it will need to be disposed asynchronously. | ||
state.Current.AsyncEnumerator = enumerator; | ||
} | ||
else | ||
{ | ||
Debug.Assert(state.Current.AsyncEnumerator is IAsyncEnumerator<TElement>); | ||
enumerator = (IAsyncEnumerator<TElement>)state.Current.AsyncEnumerator; | ||
|
||
if (state.Current.AsyncEnumeratorIsPendingCompletion) | ||
{ | ||
// converter was previously suspended due to a pending MoveNextAsync() task | ||
Debug.Assert(state.PendingTask is Task<bool> && state.PendingTask.IsCompleted); | ||
moveNextTask = new ValueTask<bool>((Task<bool>)state.PendingTask); | ||
state.Current.AsyncEnumeratorIsPendingCompletion = false; | ||
state.PendingTask = null; | ||
} | ||
else | ||
{ | ||
// converter was suspended for a different reason; | ||
// the last MoveNextAsync() call can only have completed with 'true'. | ||
moveNextTask = new ValueTask<bool>(true); | ||
} | ||
} | ||
|
||
JsonConverter<TElement> converter = GetElementConverter(ref state); | ||
|
||
// iterate through the enumerator while elements are being returned synchronously | ||
for (; moveNextTask.IsCompleted; moveNextTask = enumerator.MoveNextAsync()) | ||
{ | ||
if (!moveNextTask.Result) | ||
{ | ||
return true; | ||
} | ||
|
||
if (ShouldFlush(writer, ref state)) | ||
{ | ||
return false; | ||
} | ||
|
||
TElement element = enumerator.Current; | ||
if (!converter.TryWrite(writer, element, options, ref state)) | ||
{ | ||
return false; | ||
} | ||
} | ||
|
||
// we have a pending MoveNextAsync() call; | ||
// wrap inside a regular task so that it can be awaited multiple times; | ||
// mark the current stackframe as pending completion. | ||
Debug.Assert(state.PendingTask is null); | ||
state.PendingTask = moveNextTask.AsTask(); | ||
state.Current.AsyncEnumeratorIsPendingCompletion = true; | ||
return false; | ||
} | ||
|
||
private sealed class BufferedAsyncEnumerable : IAsyncEnumerable<TElement> | ||
{ | ||
public readonly List<TElement> _buffer = new(); | ||
|
||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously | ||
public async IAsyncEnumerator<TElement> GetAsyncEnumerator(CancellationToken _) | ||
{ | ||
foreach (TElement element in _buffer) | ||
{ | ||
yield return element; | ||
} | ||
} | ||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.