1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.Diagnostics;
6 using System.Runtime.InteropServices;
8 using System.Threading;
9 using System.Threading.Tasks;
13 // This class implements a TextWriter for writing characters to a Stream.
14 // This is designed for character output in a particular Encoding,
15 // whereas the Stream class is designed for byte input and output.
16 public class StreamWriter : TextWriter
18 // For UTF-8, the values of 1K for the default buffer size and 4K for the
19 // file stream buffer size are reasonable & give very reasonable
20 // performance for in terms of construction time for the StreamWriter and
21 // write perf. Note that for UTF-8, we end up allocating a 4K byte buffer,
22 // which means we take advantage of adaptive buffering code.
23 // The performance using UnicodeEncoding is acceptable.
24 private const int DefaultBufferSize = 1024; // char[]
25 private const int DefaultFileStreamBufferSize = 4096;
26 private const int MinBufferSize = 128;
28 private const int DontCopyOnWriteLineThreshold = 512;
30 // Bit bucket - Null has no backing store. Non closable.
31 public new static readonly StreamWriter Null = new StreamWriter(Stream.Null, UTF8NoBOM, MinBufferSize, true);
33 private Stream _stream;
34 private Encoding _encoding;
35 private Encoder _encoder;
36 private byte[] _byteBuffer;
37 private char[] _charBuffer;
40 private bool _autoFlush;
41 private bool _haveWrittenPreamble;
42 private bool _closable;
44 // We don't guarantee thread safety on StreamWriter, but we should at
45 // least prevent users from trying to write anything while an Async
46 // write from the same thread is in progress.
47 private volatile Task _asyncWriteTask;
49 private void CheckAsyncTaskInProgress()
51 // We are not locking the access to _asyncWriteTask because this is not meant to guarantee thread safety.
52 // We are simply trying to deter calling any Write APIs while an async Write from the same thread is in progress.
54 Task t = _asyncWriteTask;
56 if (t != null && !t.IsCompleted)
57 throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress);
60 // The high level goal is to be tolerant of encoding errors when we read and very strict
61 // when we write. Hence, default StreamWriter encoding will throw on encoding error.
62 // Note: when StreamWriter throws on invalid encoding chars (for ex, high surrogate character
63 // D800-DBFF without a following low surrogate character DC00-DFFF), it will cause the
64 // internal StreamWriter's state to be irrecoverable as it would have buffered the
65 // illegal chars and any subsequent call to Flush() would hit the encoding error again.
66 // Even Close() will hit the exception as it would try to flush the unwritten data.
67 // Maybe we can add a DiscardBufferedData() method to get out of such situation (like
68 // StreamReader though for different reason). Either way, the buffered data will be lost!
69 private static Encoding UTF8NoBOM => EncodingCache.UTF8NoBOM;
72 internal StreamWriter() : base(null)
73 { // Ask for CurrentCulture all the time
76 public StreamWriter(Stream stream)
77 : this(stream, UTF8NoBOM, DefaultBufferSize, false)
81 public StreamWriter(Stream stream, Encoding encoding)
82 : this(stream, encoding, DefaultBufferSize, false)
86 // Creates a new StreamWriter for the given stream. The
87 // character encoding is set by encoding and the buffer size,
88 // in number of 16-bit characters, is set by bufferSize.
90 public StreamWriter(Stream stream, Encoding encoding, int bufferSize)
91 : this(stream, encoding, bufferSize, false)
95 public StreamWriter(Stream stream, Encoding encoding, int bufferSize, bool leaveOpen)
96 : base(null) // Ask for CurrentCulture all the time
98 if (stream == null || encoding == null)
100 throw new ArgumentNullException(stream == null ? nameof(stream) : nameof(encoding));
102 if (!stream.CanWrite)
104 throw new ArgumentException(SR.Argument_StreamNotWritable);
108 throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
111 Init(stream, encoding, bufferSize, leaveOpen);
114 public StreamWriter(string path)
115 : this(path, false, UTF8NoBOM, DefaultBufferSize)
119 public StreamWriter(string path, bool append)
120 : this(path, append, UTF8NoBOM, DefaultBufferSize)
124 public StreamWriter(string path, bool append, Encoding encoding)
125 : this(path, append, encoding, DefaultBufferSize)
129 public StreamWriter(string path, bool append, Encoding encoding, int bufferSize)
132 throw new ArgumentNullException(nameof(path));
133 if (encoding == null)
134 throw new ArgumentNullException(nameof(encoding));
135 if (path.Length == 0)
136 throw new ArgumentException(SR.Argument_EmptyPath);
138 throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
140 Stream stream = new FileStream(path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read,
141 DefaultFileStreamBufferSize, FileOptions.SequentialScan);
142 Init(stream, encoding, bufferSize, shouldLeaveOpen: false);
145 private void Init(Stream streamArg, Encoding encodingArg, int bufferSize, bool shouldLeaveOpen)
148 _encoding = encodingArg;
149 _encoder = _encoding.GetEncoder();
150 if (bufferSize < MinBufferSize)
152 bufferSize = MinBufferSize;
155 _charBuffer = new char[bufferSize];
156 _byteBuffer = new byte[_encoding.GetMaxByteCount(bufferSize)];
157 _charLen = bufferSize;
158 // If we're appending to a Stream that already has data, don't write
160 if (_stream.CanSeek && _stream.Position > 0)
162 _haveWrittenPreamble = true;
165 _closable = !shouldLeaveOpen;
168 public override void Close()
171 GC.SuppressFinalize(this);
174 protected override void Dispose(bool disposing)
178 // We need to flush any buffered data if we are being closed/disposed.
179 // Also, we never close the handles for stdout & friends. So we can safely
180 // write any buffered data to those streams even during finalization, which
181 // is generally the right thing to do.
184 // Note: flush on the underlying stream can throw (ex., low disk space)
185 if (disposing /* || (LeaveOpen && stream is __ConsoleStream) */)
187 CheckAsyncTaskInProgress();
195 // Dispose of our resources if this StreamWriter is closable.
196 // Note: Console.Out and other such non closable streamwriters should be left alone
197 if (!LeaveOpen && _stream != null)
201 // Attempt to close the stream even if there was an IO error from Flushing.
202 // Note that Stream.Close() can potentially throw here (may or may not be
203 // due to the same Flush error). In this case, we still need to ensure
204 // cleaning up internal resources, hence the finally block.
218 base.Dispose(disposing);
224 public override void Flush()
226 CheckAsyncTaskInProgress();
231 private void Flush(bool flushStream, bool flushEncoder)
233 // flushEncoder should be true at the end of the file and if
234 // the user explicitly calls Flush (though not if AutoFlush is true).
235 // This is required to flush any dangling characters from our UTF-7
236 // and UTF-8 encoders.
239 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
242 // Perf boost for Flush on non-dirty writers.
243 if (_charPos == 0 && !flushStream && !flushEncoder)
248 if (!_haveWrittenPreamble)
250 _haveWrittenPreamble = true;
251 ReadOnlySpan<byte> preamble = _encoding.Preamble;
252 if (preamble.Length > 0)
254 _stream.Write(preamble);
258 int count = _encoder.GetBytes(_charBuffer, 0, _charPos, _byteBuffer, 0, flushEncoder);
262 _stream.Write(_byteBuffer, 0, count);
264 // By definition, calling Flush should flush the stream, but this is
265 // only necessary if we passed in true for flushStream. The Web
266 // Services guys have some perf tests where flushing needlessly hurts.
273 public virtual bool AutoFlush
275 get { return _autoFlush; }
279 CheckAsyncTaskInProgress();
289 public virtual Stream BaseStream
291 get { return _stream; }
294 internal bool LeaveOpen
296 get { return !_closable; }
299 internal bool HaveWrittenPreamble
301 set { _haveWrittenPreamble = value; }
304 public override Encoding Encoding
306 get { return _encoding; }
309 public override void Write(char value)
311 CheckAsyncTaskInProgress();
313 if (_charPos == _charLen)
318 _charBuffer[_charPos] = value;
326 public override void Write(char[] buffer)
330 WriteCore(buffer, _autoFlush);
334 public override void Write(char[] buffer, int index, int count)
338 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
342 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
346 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
348 if (buffer.Length - index < count)
350 throw new ArgumentException(SR.Argument_InvalidOffLen);
353 WriteCore(new ReadOnlySpan<char>(buffer, index, count), _autoFlush);
356 public override void Write(ReadOnlySpan<char> buffer)
358 if (GetType() == typeof(StreamWriter))
360 WriteCore(buffer, _autoFlush);
364 // If a derived class may have overridden existing Write behavior,
365 // we need to make sure we use it.
370 private unsafe void WriteCore(ReadOnlySpan<char> buffer, bool autoFlush)
372 CheckAsyncTaskInProgress();
374 if (buffer.Length <= 4 && // Threshold of 4 chosen based on perf experimentation
375 buffer.Length <= _charLen - _charPos)
377 // For very short buffers and when we don't need to worry about running out of space
378 // in the char buffer, just copy the chars individually.
379 for (int i = 0; i < buffer.Length; i++)
381 _charBuffer[_charPos++] = buffer[i];
386 // For larger buffers or when we may run out of room in the internal char buffer, copy in chunks.
387 // Use unsafe code until https://github.com/dotnet/coreclr/issues/13827 is addressed, as spans are
388 // resulting in significant overhead (even when the if branch above is taken rather than this
389 // else) due to temporaries that need to be cleared. Given the use of unsafe code, we also
390 // make local copies of instance state to protect against potential concurrent misuse.
392 char[] charBuffer = _charBuffer;
393 if (charBuffer == null)
395 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
398 fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer))
399 fixed (char* dstPtr = &charBuffer[0])
401 char* srcPtr = bufferPtr;
402 int count = buffer.Length;
403 int dstPos = _charPos; // use a local copy of _charPos for safety
406 if (dstPos == charBuffer.Length)
412 int n = Math.Min(charBuffer.Length - dstPos, count);
413 int bytesToCopy = n * sizeof(char);
415 Buffer.MemoryCopy(srcPtr, dstPtr + dstPos, bytesToCopy, bytesToCopy);
431 public override void Write(string value)
435 WriteCore(value.AsSpan(), _autoFlush);
440 // Optimize the most commonly used WriteLine overload. This optimization is important for System.Console in particular
441 // because of it will make one WriteLine equal to one call to the OS instead of two in the common case.
443 public override void WriteLine(string value)
445 CheckAsyncTaskInProgress();
448 WriteCore(value.AsSpan(), autoFlush: false);
450 WriteCore(new ReadOnlySpan<char>(CoreNewLine), autoFlush: true);
453 public override void WriteLine(ReadOnlySpan<char> value)
455 if (GetType() == typeof(StreamWriter))
457 CheckAsyncTaskInProgress();
458 WriteCore(value, autoFlush: false);
459 WriteCore(new ReadOnlySpan<char>(CoreNewLine), autoFlush: true);
463 // If a derived class may have overridden existing WriteLine behavior,
464 // we need to make sure we use it.
465 base.WriteLine(value);
469 #region Task based Async APIs
470 public override Task WriteAsync(char value)
472 // If we have been inherited into a subclass, the following implementation could be incorrect
473 // since it does not call through to Write() which a subclass might have overridden.
474 // To be safe we will only use this implementation in cases where we know it is safe to do so,
475 // and delegate to our base class (which will call into Write) when we are not sure.
476 if (GetType() != typeof(StreamWriter))
478 return base.WriteAsync(value);
483 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
486 CheckAsyncTaskInProgress();
488 Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
489 _asyncWriteTask = task;
494 // We pass in private instance fields of this MarshalByRefObject-derived type as local params
495 // to ensure performant access inside the state machine that corresponds this async method.
496 // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
497 private static async Task WriteAsyncInternal(StreamWriter _this, char value,
498 char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
499 bool autoFlush, bool appendNewLine)
501 if (charPos == charLen)
503 await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
504 Debug.Assert(_this._charPos == 0);
508 charBuffer[charPos] = value;
513 for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
515 if (charPos == charLen)
517 await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
518 Debug.Assert(_this._charPos == 0);
522 charBuffer[charPos] = coreNewLine[i];
529 await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
530 Debug.Assert(_this._charPos == 0);
534 _this.CharPos_Prop = charPos;
537 public override Task WriteAsync(string value)
539 // If we have been inherited into a subclass, the following implementation could be incorrect
540 // since it does not call through to Write() which a subclass might have overridden.
541 // To be safe we will only use this implementation in cases where we know it is safe to do so,
542 // and delegate to our base class (which will call into Write) when we are not sure.
543 if (GetType() != typeof(StreamWriter))
545 return base.WriteAsync(value);
552 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
555 CheckAsyncTaskInProgress();
557 Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
558 _asyncWriteTask = task;
564 return Task.CompletedTask;
568 // We pass in private instance fields of this MarshalByRefObject-derived type as local params
569 // to ensure performant access inside the state machine that corresponds this async method.
570 // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
571 private static async Task WriteAsyncInternal(StreamWriter _this, string value,
572 char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
573 bool autoFlush, bool appendNewLine)
575 Debug.Assert(value != null);
577 int count = value.Length;
582 if (charPos == charLen)
584 await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
585 Debug.Assert(_this._charPos == 0);
589 int n = charLen - charPos;
595 Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code.");
597 value.CopyTo(index, charBuffer, charPos, n);
606 for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
608 if (charPos == charLen)
610 await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
611 Debug.Assert(_this._charPos == 0);
615 charBuffer[charPos] = coreNewLine[i];
622 await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
623 Debug.Assert(_this._charPos == 0);
627 _this.CharPos_Prop = charPos;
630 public override Task WriteAsync(char[] buffer, int index, int count)
634 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
638 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
642 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
644 if (buffer.Length - index < count)
646 throw new ArgumentException(SR.Argument_InvalidOffLen);
649 // If we have been inherited into a subclass, the following implementation could be incorrect
650 // since it does not call through to Write() which a subclass might have overridden.
651 // To be safe we will only use this implementation in cases where we know it is safe to do so,
652 // and delegate to our base class (which will call into Write) when we are not sure.
653 if (GetType() != typeof(StreamWriter))
655 return base.WriteAsync(buffer, index, count);
660 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
663 CheckAsyncTaskInProgress();
665 Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: default);
666 _asyncWriteTask = task;
671 public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
673 if (GetType() != typeof(StreamWriter))
675 // If a derived type may have overridden existing WriteASync(char[], ...) behavior, make sure we use it.
676 return base.WriteAsync(buffer, cancellationToken);
681 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
684 CheckAsyncTaskInProgress();
686 if (cancellationToken.IsCancellationRequested)
688 return Task.FromCanceled(cancellationToken);
691 Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: cancellationToken);
692 _asyncWriteTask = task;
696 // We pass in private instance fields of this MarshalByRefObject-derived type as local params
697 // to ensure performant access inside the state machine that corresponds this async method.
698 // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
699 private static async Task WriteAsyncInternal(StreamWriter _this, ReadOnlyMemory<char> source,
700 char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
701 bool autoFlush, bool appendNewLine, CancellationToken cancellationToken)
704 while (copied < source.Length)
706 if (charPos == charLen)
708 await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
709 Debug.Assert(_this._charPos == 0);
713 int n = Math.Min(charLen - charPos, source.Length - copied);
714 Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code.");
716 source.Span.Slice(copied, n).CopyTo(new Span<char>(charBuffer, charPos, n));
723 for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
725 if (charPos == charLen)
727 await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
728 Debug.Assert(_this._charPos == 0);
732 charBuffer[charPos] = coreNewLine[i];
739 await _this.FlushAsyncInternal(true, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
740 Debug.Assert(_this._charPos == 0);
744 _this.CharPos_Prop = charPos;
747 public override Task WriteLineAsync()
749 // If we have been inherited into a subclass, the following implementation could be incorrect
750 // since it does not call through to Write() which a subclass might have overridden.
751 // To be safe we will only use this implementation in cases where we know it is safe to do so,
752 // and delegate to our base class (which will call into Write) when we are not sure.
753 if (GetType() != typeof(StreamWriter))
755 return base.WriteLineAsync();
760 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
763 CheckAsyncTaskInProgress();
765 Task task = WriteAsyncInternal(this, ReadOnlyMemory<char>.Empty, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
766 _asyncWriteTask = task;
772 public override Task WriteLineAsync(char value)
774 // If we have been inherited into a subclass, the following implementation could be incorrect
775 // since it does not call through to Write() which a subclass might have overridden.
776 // To be safe we will only use this implementation in cases where we know it is safe to do so,
777 // and delegate to our base class (which will call into Write) when we are not sure.
778 if (GetType() != typeof(StreamWriter))
780 return base.WriteLineAsync(value);
785 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
788 CheckAsyncTaskInProgress();
790 Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
791 _asyncWriteTask = task;
797 public override Task WriteLineAsync(string value)
801 return WriteLineAsync();
804 // If we have been inherited into a subclass, the following implementation could be incorrect
805 // since it does not call through to Write() which a subclass might have overridden.
806 // To be safe we will only use this implementation in cases where we know it is safe to do so,
807 // and delegate to our base class (which will call into Write) when we are not sure.
808 if (GetType() != typeof(StreamWriter))
810 return base.WriteLineAsync(value);
815 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
818 CheckAsyncTaskInProgress();
820 Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
821 _asyncWriteTask = task;
827 public override Task WriteLineAsync(char[] buffer, int index, int count)
831 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
835 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
839 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
841 if (buffer.Length - index < count)
843 throw new ArgumentException(SR.Argument_InvalidOffLen);
846 // If we have been inherited into a subclass, the following implementation could be incorrect
847 // since it does not call through to Write() which a subclass might have overridden.
848 // To be safe we will only use this implementation in cases where we know it is safe to do so,
849 // and delegate to our base class (which will call into Write) when we are not sure.
850 if (GetType() != typeof(StreamWriter))
852 return base.WriteLineAsync(buffer, index, count);
857 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
860 CheckAsyncTaskInProgress();
862 Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
863 _asyncWriteTask = task;
868 public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
870 if (GetType() != typeof(StreamWriter))
872 return base.WriteLineAsync(buffer, cancellationToken);
877 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
880 CheckAsyncTaskInProgress();
882 if (cancellationToken.IsCancellationRequested)
884 return Task.FromCanceled(cancellationToken);
887 Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: cancellationToken);
888 _asyncWriteTask = task;
894 public override Task FlushAsync()
896 // If we have been inherited into a subclass, the following implementation could be incorrect
897 // since it does not call through to Flush() which a subclass might have overridden. To be safe
898 // we will only use this implementation in cases where we know it is safe to do so,
899 // and delegate to our base class (which will call into Flush) when we are not sure.
900 if (GetType() != typeof(StreamWriter))
902 return base.FlushAsync();
905 // flushEncoder should be true at the end of the file and if
906 // the user explicitly calls Flush (though not if AutoFlush is true).
907 // This is required to flush any dangling characters from our UTF-7
908 // and UTF-8 encoders.
911 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
914 CheckAsyncTaskInProgress();
916 Task task = FlushAsyncInternal(true, true, _charBuffer, _charPos);
917 _asyncWriteTask = task;
922 private int CharPos_Prop
924 set { _charPos = value; }
927 private bool HaveWrittenPreamble_Prop
929 set { _haveWrittenPreamble = value; }
932 private Task FlushAsyncInternal(bool flushStream, bool flushEncoder,
933 char[] sCharBuffer, int sCharPos, CancellationToken cancellationToken = default)
935 if (cancellationToken.IsCancellationRequested)
937 return Task.FromCanceled(cancellationToken);
940 // Perf boost for Flush on non-dirty writers.
941 if (sCharPos == 0 && !flushStream && !flushEncoder)
943 return Task.CompletedTask;
946 Task flushTask = FlushAsyncInternal(this, flushStream, flushEncoder, sCharBuffer, sCharPos, _haveWrittenPreamble,
947 _encoding, _encoder, _byteBuffer, _stream, cancellationToken);
954 // We pass in private instance fields of this MarshalByRefObject-derived type as local params
955 // to ensure performant access inside the state machine that corresponds this async method.
956 private static async Task FlushAsyncInternal(StreamWriter _this, bool flushStream, bool flushEncoder,
957 char[] charBuffer, int charPos, bool haveWrittenPreamble,
958 Encoding encoding, Encoder encoder, Byte[] byteBuffer, Stream stream, CancellationToken cancellationToken)
960 if (!haveWrittenPreamble)
962 _this.HaveWrittenPreamble_Prop = true;
963 byte[] preamble = encoding.GetPreamble();
964 if (preamble.Length > 0)
966 await stream.WriteAsync(preamble, 0, preamble.Length, cancellationToken).ConfigureAwait(false);
970 int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder);
973 await stream.WriteAsync(byteBuffer, 0, count, cancellationToken).ConfigureAwait(false);
976 // By definition, calling Flush should flush the stream, but this is
977 // only necessary if we passed in true for flushStream. The Web
978 // Services guys have some perf tests where flushing needlessly hurts.
981 await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
985 } // class StreamWriter