Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does FileStream.ReadAsync sometimes complete synchronously?

Give this a try. Setup a new Windows Forms application with a single button and the following code for the button click event:

private async void button1_Click(object sender, EventArgs e)
{
    using (var file = File.OpenRead(@"C:\Temp\Sample.txt"))
    {
        byte[] buffer = new byte[4096];
        int threadId = Thread.CurrentThread.ManagedThreadId;
        int read = await file.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
        Debug.Assert(threadId != Thread.CurrentThread.ManagedThreadId);
    }
}

Then run the application and click the button rapidly. If your experience is anything like mine you’ll find that sometimes it works as expected, however, other times the Debug.Assert will fail.

Based on my understanding of ConfigureAwait as explained on Stephen Cleary’s blog, passing false for continueOnCapturedContext should instruct the task to not sync back to the “main” context (UI thread in this case) and that execution should continue on the thread pool.

Why then would the assert fail randomly? I can only assume that ReadAsync will sometimes not complete on a background thread, i.e. it will complete synchronously.

This behavior is in line with KB 156932 - Asynchronous Disk I/O Appears as Synchronous on Windows:

Most I/O drivers (disk, communications, and others) have special case code where, if an I/O request can be completed "immediately," the operation will be completed and the ReadFile or WriteFile function will return TRUE. In all ways, these types of operations appear to be synchronous. For a disk device, typically, an I/O request can be completed "immediately" when the data is cached in memory.

Are my assumptions and test application correct? Can the ReadAsync method sometimes complete synchronously? Is there a way to guarantee that execution will always continue on a background thread?

This issue is wreaking havoc on my application which uses COM objects that require me to know which thread is executing at all times.

Windows 7 64-bit, .NET 4.5.1

like image 736
Jacob Avatar asked Mar 19 '14 01:03

Jacob


2 Answers

Can the ReadAsync method sometimes complete synchronously?

Yes. This is not uncommon due to buffers both at the OS level and within the .NET stream types.

Is there a way to guarantee that execution will always continue on a background thread?

If you always want code to execute on a thread pool thread, then use Task.Run:

private async void button1_Click(object sender, EventArgs e)
{
  using (var file = File.OpenRead(@"C:\Temp\Sample.txt"))
  {
    byte[] buffer = new byte[4096];
    int threadId = Thread.CurrentThread.ManagedThreadId;
    int read = await file.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
    await Task.Run(() =>
    {
      Debug.Assert(threadId != Thread.CurrentThread.ManagedThreadId);
    }).ConfigureAwait(false);
  }
}

ConfigureAwait is an optimization hint, not a command to move to a background thread. ConfigureAwait has no effect if the operation is already completed. In this case, await follows a "fast path" that essentially means it continues synchronously, so any ConfigureAwait hints are ignored.

like image 193
Stephen Cleary Avatar answered Oct 30 '22 20:10

Stephen Cleary


If the file read completes synchronously before the call to await, I am fairly certain that none of the async compiler magic happens and execution continues synchronously on the calling thread. I would use Task.Run(() => file.ReadAsync(buffer, 0, buffer.Length)) to queue the work to the thread pool if I wanted to guarantee that it starts on a thread pool thread.

like image 35
Tim Destan Avatar answered Oct 30 '22 19:10

Tim Destan