Question

Why does creating a FileStream with FileOptions.Asynchronous cause FileStream.BeginRead to block the calling thread?

Here is the code snippet:

private static Task<int> ReadFileAsync(string filePath)
  {
     var file = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, 64 * 1024, FileOptions.Asynchronous);
     FileInfo fi = new FileInfo(filePath);
     byte[] buffer = new byte[fi.Length];
     Task<int> task = Task<int>.Factory.FromAsync(file.BeginRead, file.EndRead, buffer, 0, buffer.Length, null);         
     return task.ContinueWith(t =>
     {
        file.Close();
        Console.WriteLine("Done ReadFileAsync, read " + t.Result + " bytes.");
        return t.Result;
     });
  }

When digging around in the MSFT FileStream code using JetBrains dotPeek it seems like there is a bug in their code:

      if (!this._isAsync)
    return base.BeginRead(array, offset, numBytes, userCallback, stateObject);
  else
    return (IAsyncResult) this.BeginReadAsync(array, offset, numBytes, userCallback, stateObject);

The BeginRead method actually seems to do reading asynchronously by scheduling a Task, but the BeginReadAsync method actually ends up doing a synchronous read. So their method naming nomenclature is backwards and the logic of which method is called is wrong. BeginRead should be called if this._isAsync == true.

So it seems that to get FileStream.BeginRead to return right away (asynchronously schedule a read) you actually have to set the useAsync parameter in the cosntructor to false.

Was it helpful?

Solution

Here's a knowledge base article that lists all the ways that can cause code you desire to be executed asynchronously to actually run synchronously.

Asynchronous Disk I/O Appears as Synchronous on Windows NT, Windows 2000, and Windows XP

Does anything on the list apply to your situation?

Have you tried implementing the same behavior with .NET 4.5's ReadAsync method?

I am quoting from MSDN:

In the .NET Framework 4 and earlier versions, you have to use methods such as BeginRead and EndRead to implement asynchronous I/O operations. These methods are still available in the .NET Framework 4.5 to support legacy code; however, the new async methods, such as ReadAsync, WriteAsync, CopyToAsync, and FlushAsync, help you implement asynchronous I/O operations more easily.

EDIT I am reproducing your issue with a 256MB file on OCZ Vertex 2 with ICH10 and Windows 7. I am having to generate the file, reboot the PC to clear file cache, and then try and read the same file.

using System;
using System.Threading.Tasks;
using System.IO;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApplication4
{
   class Program
   {
      static void Main(string[] args)
      {
         string fileName = @"C:\Temp\a1.txt";
         int arraySize = 512 * 1024 * 1024;
         var bytes = new byte[arraySize];
         new Random().NextBytes(bytes);

          // This prints false, as expected for async call
         var callback = new AsyncCallback(result =>
                             Console.WriteLine("Completed Synchronously: " + result.CompletedSynchronously));

         try
         {
            // Use this method to generate file...
            //WriteFileWithRandomBytes(fileName, arraySize, bytes, callback);

            Console.WriteLine("ReadFileAsync invoked at " + DateTimeOffset.Now);
            var task = ReadFileAsync(fileName);
            Console.WriteLine("ReadFileAsync completed at " + DateTimeOffset.Now);

            Task.WaitAll(task);
            Console.WriteLine("Wait on a read task completed at " + DateTimeOffset.Now);
         }
         finally
         {
            if (File.Exists(fileName))
               File.Delete(fileName);
         }
      }

      private static void WriteFileWithRandomBytes(string fileName, int arraySize, byte[] bytes, AsyncCallback callback)
      {
         using (var fileStream = new FileStream(fileName,
            FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 128 * 1024, FileOptions.Asynchronous))
         {
            Console.WriteLine("BeginWrite invoked at " + DateTimeOffset.Now);
            var asyncResult = fileStream.BeginWrite(bytes, 0, arraySize, callback, null);


            Console.WriteLine("BeginWrite completed at " + DateTimeOffset.Now);
            // completes in 6 seconds or so...  Expecting instantaneous return instead of blocking

            // I expect runtime to block here...
            Task.WaitAll(Task.Factory.FromAsync(asyncResult, fileStream.EndWrite));

            // or at least when flushing the stream on the following end-curly
         }
      }


      private static Task<int> ReadFileAsync(string filePath)
      {
         FileInfo fi = new FileInfo(filePath);
         byte[] buffer = new byte[fi.Length];

         var file = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None, 64 * 1024, FileOptions.Asynchronous);
         Task<int> task = Task<int>.Factory.FromAsync(file.BeginRead, file.EndRead, buffer, 0, buffer.Length, null);
         return task.ContinueWith(t =>
         {
            file.Close();
            Console.WriteLine("Done ReadFileAsync, read " + t.Result + " bytes.");
            return t.Result;
         });
      }
   }
}

When all else fails, here's the reference to unmanaged API documentation.

OTHER TIPS

Did you try to pass in a valid stateObject, instead of null? Compare to code at http://msdn.microsoft.com/en-us/library/kztecsys(v=vs.100).aspx

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top