【问题标题】:Does FileStream.ReadAsync sometimes complete synchronously?FileStream.ReadAsync 有时会同步完成吗?
【发布时间】:2014-03-19 20:44:55
【问题描述】:

试试这个。使用单个按钮和按钮单击事件的以下代码设置新的 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 线程),并且应该继续执行线程池。

那么为什么断言会随机失败呢?我只能假设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

【问题讨论】:

  • 您没有以异步模式打开文件有什么原因吗?
  • 当使用指定“异步模式”的FileStream 重载打开文件时,我发现上述行为没有任何变化。

标签: c# asynchronous task-parallel-library


【解决方案1】:

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

是的。由于操作系统级别和 .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 提示都会被忽略。

【讨论】:

  • 谢谢斯蒂芬。那么我一个人在想ConfigureAwait 有点误导吗?我在 MSDN 上找到的任何内容都没有向我暗示这不仅仅是一个事实。例如,您的代码示例在Task.Run 上使用ConfigureAwait(false)。我有什么保证Task.Run 将在预期的上下文中完成,而不是FileStream.ReadAsync 使用相同的方法?
  • 如果没有ConfigureAwait 调用,我的代码将完全一样地工作;它的效率会稍低一些。这是保证在线程池线程上运行的内部 Task.Run,而不是任何延续。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 2019-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多