I spotted the source of confusion.
StreamWriter
flushes synchronously by default unless you specifically flush asynchronously.
Stream.Flush
is called when closing, and of course, flushing.
This will invoke Write
rather than WriteAsync
in the messageWriter
stream:
using (var sw = new StreamWriter(messageWriter, Encoding.UTF8))
await sw.WriteAsync("Hi");
The StreamWriter
will buffer the "Hi" message, and it will be released to the underlying stream on the Flush
invocation synchronously when closing the StreamWriter
.
So the asynchronous call to StreamWriter.WriteAsync
could have no effect on the underlying stream (because the buffering), and when calling StreamWriter.Close
, StreamWriter.Flush
is called synchronously, and therefore the Flush
method on the underlying stream.
This code also calls Write
synchronously:
using (var sw = new StreamWriter(messageWriter, Encoding.UTF8))
{
sw.AutoFlush = true; // will call Write here to send the UTF8 BOM
await sw.WriteAsync("Hi"); // Write the actual data
}
obviously also this this:
using (var sw = new StreamWriter(messageWriter, Encoding.UTF8))
{
await sw.WriteAsync("Hi");
sw.Flush();
}
This will call WriteAsync
:
using (var sw = new StreamWriter(messageWriter, Encoding.UTF8))
{
await sw.WriteAsync("Hi");
await sw.FlushAsync()
}
I have created a simple class to track how the methods are called:
public class MSWrite : MemoryStream
{
public override bool CanWrite { get { return true; } }
public override void Write(byte[] buffer, int offset, int count)
{
Console.WriteLine("Write");
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
Console.WriteLine("WriteAsync");
return Task.Run(() => { });
}
}
And this test:
Console.WriteLine("Simple");
using(var ms = new MSWrite())
using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
{
await sw.WriteAsync("Hi");
}
Console.WriteLine();
Console.WriteLine("AutoFlush");
using (var ms = new MSWrite())
using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
{
sw.AutoFlush = true;
await sw.WriteAsync("Hi");
}
Console.WriteLine();
Console.WriteLine("Flush async");
using (var ms = new MSWrite())
using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
{
await sw.WriteAsync("Hi");
await sw.FlushAsync();
}
Yields:
Simple
Write
Write
AutoFlush
Write
WriteAsync
Flush async
WriteAsync
WriteAsync
The first write is the UTF8 BOM, the second is the actual data.