private static async Task WriteAsyncCore <TValue>( Stream utf8Json, TValue value, Type inputType, JsonSerializerOptions?options, CancellationToken cancellationToken) { // We flush the Stream when the buffer is >=90% of capacity. // This threshold is a compromise between buffer utilization and minimizing cases where the buffer // needs to be expanded\doubled because it is not large enough to write the current property or element. // We check for flush after each object property and array element is written to the buffer. // Once the buffer is expanded to contain the largest single element\property, a 90% thresold // means the buffer may be expanded a maximum of 4 times: 1-(1\(2^4))==.9375. const float FlushThreshold = .9f; if (options == null) { options = JsonSerializerOptions.s_defaultOptions; } JsonWriterOptions writerOptions = options.GetWriterOptions(); using (var bufferWriter = new PooledByteBufferWriter(options.DefaultBufferSize)) using (var writer = new Utf8JsonWriter(bufferWriter, writerOptions)) { // We treat typeof(object) special and allow polymorphic behavior. if (inputType == JsonTypeInfo.ObjectType && value != null) { inputType = value !.GetType(); } WriteStack state = new WriteStack { CancellationToken = cancellationToken }; JsonConverter converterBase = state.Initialize(inputType, options, supportContinuation: true); bool isFinalBlock; try { do { state.FlushThreshold = (int)(bufferWriter.Capacity * FlushThreshold); try { isFinalBlock = WriteCore(converterBase, writer, value, options, ref state); } finally { if (state.PendingAsyncDisposables?.Count > 0) { await state.DisposePendingAsyncDisposables().ConfigureAwait(false); } } await bufferWriter.WriteToStreamAsync(utf8Json, cancellationToken).ConfigureAwait(false); bufferWriter.Clear(); if (state.PendingTask is not null) { try { await state.PendingTask.ConfigureAwait(false); } catch { // Exceptions will be propagated elsewhere // TODO https://github.com/dotnet/runtime/issues/22144 } } } while (!isFinalBlock); } catch { await state.DisposePendingDisposablesOnExceptionAsync().ConfigureAwait(false); throw; } } }
private static async Task WriteStreamAsync <TValue>( Stream utf8Json, TValue value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken) { JsonSerializerOptions options = jsonTypeInfo.Options; JsonWriterOptions writerOptions = options.GetWriterOptions(); using (var bufferWriter = new PooledByteBufferWriter(options.DefaultBufferSize)) using (var writer = new Utf8JsonWriter(bufferWriter, writerOptions)) { WriteStack state = new WriteStack { CancellationToken = cancellationToken }; JsonConverter converter = state.Initialize(jsonTypeInfo, supportContinuation: true); bool isFinalBlock; try { do { state.FlushThreshold = (int)(bufferWriter.Capacity * FlushThreshold); try { isFinalBlock = WriteCore(converter, writer, value, options, ref state); await bufferWriter.WriteToStreamAsync(utf8Json, cancellationToken).ConfigureAwait(false); bufferWriter.Clear(); } finally { // Await any pending resumable converter tasks (currently these can only be IAsyncEnumerator.MoveNextAsync() tasks). // Note that pending tasks are always awaited, even if an exception has been thrown or the cancellation token has fired. if (state.PendingTask is not null) { try { await state.PendingTask.ConfigureAwait(false); } catch { // Exceptions should only be propagated by the resuming converter // TODO https://github.com/dotnet/runtime/issues/22144 } } // Dispose any pending async disposables (currently these can only be completed IAsyncEnumerators). if (state.CompletedAsyncDisposables?.Count > 0) { await state.DisposeCompletedAsyncDisposables().ConfigureAwait(false); } } } while (!isFinalBlock); } catch { // On exception, walk the WriteStack for any orphaned disposables and try to dispose them. await state.DisposePendingDisposablesOnExceptionAsync().ConfigureAwait(false); throw; } } }