The AsyncStream class wraps an existing stream and provides asynchronous I/O via the standard Read(), ReadByte(), Write(), and WriteByte() methods; if called on the wrapped stream directly these would require waiting for I/O to the underlying device (such as a disk) but the AsyncStream asynchronously and automatically fills or flushes its internal buffer from or to the wrapped stream using a background thread and will never block so long as sufficient bytes are available in the buffer. Even if your application logic is already built around synchronous I/O, simply replacing the original stream with an AsyncStream can increase performance, sometimes dramatically, with as little as a single-line change to your code [example]. It can also improve performance when passed to methods that manipulate streams, such as HashAlgorithm.ComputeHash() [example]. AsyncStreams additionally provide elegant exception handling, properties for fine-tuning performance and behavior when desired, and, like BetterBufferedStreams, efficient in-buffer seeks.
Multiple processors are not required to benefit from asynchronous I/O. Thanks to DMA, file and network I/O (among others) can always be performed asynchronously on modern computers. Data is copied to/from the device into memory without CPU intervention, allowing other code to run while the transfer proceeds.
Download an evaluation copy of the library to try it in your own applications (or use the included test package). You can also view the documentation for this class online.
1. When using FileStreams, the number of bytes read or written at once can have an unexpected effect on performance. On the test machine FileStream I/O speed varied depending on how large the buffer was (and consequently how many bytes were requested at once from the OS). Below are the times taken to read a 4GB file, requesting 100KB at a time with six FileStreams of varying buffer size:

Thus, for this particular machine, reading 512KB from the disk at a time is relatively slow; any class wrapping a FileStream (such BufferedStream, BetterBufferedStream, and AsyncStream) will also be affected. Note, however, that AsyncStreams read between {buffer size}/4 and {buffer size}/2 bytes at a time by default, so a AsyncStream with a 1MB buffer will read up to as many bytes at a time as a FileStream or BetterBufferedStream with a 512KB buffer.
2. AsyncStreams have a MinimumIOSize and a MaximumIOSize property that dictate how much data will be requested from or written to the underlying stream in a single operation. This can be very useful when using streams with varying performance characteristics; continuing from the above example, if we wanted to wrap a FileStream in an AsyncStream with a 1MB buffer, we could set MinimumIOSize = MaximumIOSize = 102400 (100KB) to ensure that all I/O to the FileStream is done in chunks of 100KB rather than the default 256KB-512KB (which would be slower).
3. Check the Available property before reading or writing to always avoid blocking during a Read() or Write(). A Read() will never block if Available > 0, while a Write() will block only if the number of bytes to be written exceeds the number available.
4. The priority of the background thread used for I/O to the underlying stream can be altered with the BackgroundThreadPriority property.
5. If you are wrapping the AsyncStream in another stream that automatically flushes its underlying stream, set the AsyncStream's IgnoreFlush property to true to ignore calls to the Flush() method.
If the AsyncStream encounters an exception while asynchronously reading or writing to the underlying stream, it stops the background thread temporarily and sets the AsynchronousException property. This exception is thrown the next time a method is called or the Position or Mode properties are set. The AsyncStream then "forgets" the exception and the AsyncStream can be used again as normal. Example of resuming after a "not enough space on the disk" exception.