Using BinaryWriter or BinaryReader in async code

Yusuf Tarık Günaydın picture Yusuf Tarık Günaydın · Feb 15, 2016 · Viewed 8.4k times · Source

I have a list of float to write to a file. The code below does the thing but it is synchronous.

List<float> samples = GetSamples();

using (FileStream stream = File.OpenWrite("somefile.bin"))
using (BinaryWriter binaryWriter = new BinaryWriter(stream, Encoding.Default, true))
{
    foreach (var sample in samples)
    {
        binaryWriter.Write(sample);
    }
}

I want to do the operation asynchronously but the BinaryWriter does not support async operations, which is normal since it just only writes a few bytes each time. But most of the time the operation uses file I/O and I think it can and should be asynchronous.

I tried to write to a MemoryStream with the BinaryWriter and when that finished I copied the MemoryStream to the FileStream with CopyToAsync, however this caused a performance degradation (total time) up to 100% with big files.

How can I convert the whole operation to asynchronous?

Answer

Ben Voigt picture Ben Voigt · Feb 15, 2016

Normal write operations usually end up being completed asynchronously anyway. The OS accepts writes immediately into the write cache, and flushes it to disk at some later time. Your application isn't blocked by the actual disk writes.

Of course, if you are writing to a removable drive then write cache is typically disabled and your program will be blocked.


I will recommend that you can dramatically reduce the number of operations by transferring a large block at a time. To wit:

  1. Allocate a new T[BlockSize] of your desired block size.
  2. Allocate a new byte[BlockSize * sizeof (T)]
  3. Use List<T>.CopyTo(index, buffer, 0, buffer.Length) to copy a batch out of the list.
  4. Use Buffer.BlockCopy to get the data into the byte[].
  5. Write the byte[] to your stream in a single operation.
  6. Repeat 3-5 until you reach the end of the list. Careful about the final batch, which may be a partial block.