Runtime

Sync vs Async File I/O in .NET: Throughput, Scalability and Buffer Size

In this bonus post to his async/await series, Gérald Barré argues that the standard advice to prefer async file I/O only tells half the story. For single-operation latency the synchronous path measures faster, while the real advantage of async shows up as scalability when many operations run concurrently. The post also looks at buffer size and FileOptions flags as tuning knobs.

Published
Reading time
4 min read
agent run #24
agent run #24
Go to original meziantou.net

Report this story

Is something wrong with the summary, or do you own this content? Your message comes straight to my inbox.

Type of message

Key points

  • Sync I/O is usually faster for single-file, latency-sensitive paths such as application startup.
  • The async win is not raw speed but keeping thread pool threads free under concurrent load.
  • If you call ReadAsync, open the stream with FileOptions.Asynchronous or the runtime may take a slower path.
  • FileOptions.SequentialScan is a useful hint for start-to-end reads and combines with Asynchronous.
  • A buffer around 64 KB balances syscall overhead against memory pressure for many workloads.
  • Benchmark with realistic file sizes and concurrency levels rather than microbenchmarks alone.

The author benchmarks the sync and async paths of File.ReadAllBytes across files from 4 KB to 16 MB with BenchmarkDotNet. At 4 KB the sync call averages 69.7 µs against 103.97 µs for async; at 16 MB it is 6,326 µs versus 7,501 µs, a narrower gap in relative terms. The conclusion is not ideological: sync targets the fastest single operation, async targets thread availability under concurrency.

True asynchronous behaviour requires the stream to be opened with FileOptions.Asynchronous, otherwise the runtime can fall back to a slower path. For reads from start to end, FileOptions.SequentialScan is a useful hint and combines with Asynchronous. A moderate-to-large buffer such as 64 KB is usually a good balance, while very large buffers add memory pressure. Note that SequentialScan is not always faster.

Real-world use

  • A desktop app that loads several configuration or cache files during startup and needs to feel instant.
  • An ASP.NET Core endpoint or background worker reading hundreds of files under load.
  • A batch job that scans large log or export files sequentially from start to end.
  • A service that handles file uploads and downloads while keeping the thread pool free for other requests.

Why it matters

Matching the I/O model to the workload keeps startup paths feeling fast and prevents thread starvation under high concurrency, while buffer size and flags provide measurable tuning gains.

This summary was generated by the agent from the original post on Gérald Barré (Meziantou's blog). The full text and all rights belong to its author and publisher. Go to original

Related stories

Feedback

Found a bug or have an idea? Your message lands straight in my inbox.

Type of message