Did you know that asynchronous file I/O in .NET is not just about calling FileStream.BeginRead() or BeginWrite() in place of Read() or Write()? You should also make sure that the FileStream is opened for asynchronous operations, otherwise you’ll quietly get less performant ‘mock’ async operations that just execute synchronous I/O on the thread pool, rather than using true overlapped I/O at the Win32 level.
The natural starting point for creating a FileStream is the static File.Open() method, the documentation for which mentions nothing about synchronicity of the FileStream that is created! Nor does it allow you to provide FileOptions (which are used to specify the magic FileOptions.Asynchronous flag).
Instead, the FileStream is created with FileOptions.None. Any asynchronous operations are quietly faked by the obliging implementation of the Stream base class, which merely wraps the corresponding synchronous method in a delegate and invokes it on the thread pool using the BeginInvoke() method.
This is a deviation from the usual ‘pit of success’ design philosophy, where everything in .NET seems to work as you think it would, without a need to closely read the documentation and/or gradually discover obscure catches and gotchas over time.
Admittedly I’ve never actually used asynchronous file I/O (for the applications I’ve worked on have used databases, queues, and other remote data persistence rather than local files) or else I might have read the FileStream.BeginRead() and BeginWrite() documentation a little more closely:
FileStream provides two different modes of operation: synchronous I/O and asynchronous I/O. While either can be used, the underlying operating system resources might allow access in only one of these modes. By default, FileStream opens the operating system handle synchronously. In Windows, this slows down asynchronous methods. If asynchronous methods are used, use the FileStream(String, FileMode, FileAccess, FileShare, Int32, Boolean) constructor.
That last Boolean parameter to the FileStream constructor is called useAsync and, if true, results in FileOptions.Asynchronous being used (or you can also use the other constructor overload which takes FileOptions in the last parameter, and specify FileOptions.Asynchronous yourself).
The default implementation of BeginRead on a stream calls the Read method synchronously, which means that Read might block on some streams. However, instances of classes such as FileStream and NetworkStream fully support asynchronous operations if the instances have been opened asynchronously. Therefore, calls to BeginRead will not block on those streams. You can override BeginRead (by using async delegates, for example) to provide asynchronous behavior.
I think this documentation is out of date, or at least a little unclear. The default implementation of BeginRead does not call Read synchronously — Reflector shows that it calls Read by wrapping it in a delegate and calling BeginInvoke, which would result in it being called on a thread pool thread. This is an asynchronous call (with respect to the caller of BeginRead).
Perhaps the documentation is out of date, since it also suggests "using async delegates" to implement your own asynchronous behaviour — what advantage would that give you over the default implementation which does just the same?
As ever, the truth lies in Reflector.
In summary, if you want to do asynchronous file I/O:
Finally, if you’re using asynchronous I/O you must care about performance, so don’t forget to measure, measure, measure! And heed the warning hidden in the documentation of that useAsync parameter:
useAsync Specifies whether to use asynchronous I/O or synchronous I/O. However, note that the underlying operating system might not support asynchronous I/O, so when specifying true, the handle might be opened synchronously depending on the platform. When opened asynchronously, the BeginRead and BeginWrite methods perform better on large reads or writes, but they might be much slower for small reads or writes. If the application is designed to take advantage of asynchronous I/O, set the useAsync parameter to true. Using asynchronous I/O correctly can speed up applications by as much as a factor of 10, but using it without redesigning the application for asynchronous I/O can decrease performance by as much as a factor of 10.
written on 27-May-2012
Ant [http://antix.co.uk] says:
Thanks, a good read
this is getting more and more important now we're all focused on responsive apps for touch and metro