5

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.

dmg
  • 608
  • 8
  • 15
  • What do you intend to show with the posted code snippet? I cannot derive any information from it. – usr Nov 09 '12 at 20:46
  • the posted code above will reproduce the behavior where the executing thread blocks on line Task.Factory.FromAsync(...) until the read of the file is complete. So in essence it does the opposite of what I expect and reads the file synchronously instead of asynchronously. This behavior is what has me puzzled... – dmg Nov 10 '12 at 20:15

2 Answers2

5

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.

GregC
  • 7,737
  • 2
  • 53
  • 67
  • I am not sure, if this applies or not. It's hard to say. – dmg Nov 11 '12 at 22:14
  • Are you able to repro this behavior on your machine using the code snippet that I posted? – dmg Nov 11 '12 at 22:14
  • I've tried implementing this behavior with async/await and FileStream.ReadAsync method and I don't see this behavior. I see expected asynchronous read behavior. – dmg Nov 12 '12 at 14:33
0

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

Roger
  • 1
  • 1