FileStream.ReadAsync有时会同步完成吗?

时间:2014-03-19 01:22:08

标签: c# asynchronous task-parallel-library

试一试。使用单个按钮设置新的Windows窗体应用程序,并为按钮单击事件设置以下代码:

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);
    }
}

然后运行应用程序并快速单击按钮。如果您的体验与我的一样,您会发现有时它会按预期工作,但是,有时Debug.Assert会失败。

根据我对ConfigureAwait as explained on Stephen Cleary’s blog的理解,为false传递continueOnCapturedContext应指示任务不同步回“主”上下文(此处的UI线程) case)并且该执行应该继续在线程池上。

为什么断言会随机失败?我只能假设ReadAsync有时无法在后台线程上完成,即它将同步完成。

此行为符合KB 156932 - Asynchronous Disk I/O Appears as Synchronous on Windows

  

大多数I / O驱动程序(磁盘,通信和其他)都有特殊情况   代码,如果I / O请求可以完成"立即,"该   操作将完成并具有ReadFile或WriteFile功能   将返回TRUE。在所有方面,这些类型的操作似乎都是   同步。对于磁盘设备,通常可以是I / O请求   完成"立即"当数据缓存在内存中时。

我的假设和测试应用是否正确? ReadAsync方法有时可以同步完成吗?有没有办法保证执行将始终在后台线程上继续?

这个问题对我的应用程序造成了严重破坏,该应用程序使用COM对象,要求我知道哪个线程一直在执行。

Windows 7 64位,.NET 4.5.1

3 个答案:

答案 0 :(得分:3)

  

ReadAsync方法有时可以同步完成吗?

是。由于OS级别和.NET流类型中的缓冲区,这并不罕见。

  

有没有办法保证执行将始终在后台线程上继续?

如果您总是希望代码在线程池线程上执行,那么请使用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是一个优化提示,而不是移动到后台线程的命令。如果操作已经完成,则ConfigureAwait无效。在这种情况下,await遵循"fast path",这实际上意味着它会继续同步,因此会忽略任何ConfigureAwait提示。

答案 1 :(得分:2)

如果文件读取在调用await之前同步完成,我相当确定没有异步编译器魔术发生并且执行在调用线程上同步继续。如果我想保证它在线程池线程上启动,我会使用Task.Run(() => file.ReadAsync(buffer, 0, buffer.Length))将工作排队到线程池。

答案 2 :(得分:0)

await上使用Task时,如果任务已完成,则代码将同步执行。然后在该任务上调用ConfigureAwait将没有任何区别,因为不会涉及其他线程。

听起来好像数据在内存中(因为你正在重复读取同一个文件)file.ReadAsync可以同步完成,因此它将在同一个线程上完成。