【问题标题】:Is "sequential" file I/O with System.IO.File helper methods safe?使用 System.IO.File 辅助方法的“顺序”文件 I/O 是否安全?
【发布时间】:2015-09-07 07:52:04
【问题描述】:

我刚看到这个问题:Is it safe to use static methods on File class in C#?。总而言之,OP 有一个IOException,因为此 ASP.NET 代码 sn-p 中正在使用文件:

var text= File.ReadAllText("path-to-file.txt");
// Do something with text
File.WriteAllText("path-to-file.txt");

我的第一个想法是,由于多个 ASP.NET 重叠请求,这是一个简单并发访问问题。我会解决将 I/O 集中到一个同步的线程安全类(或删除文件以支持其他东西)的问题。我阅读了两个答案,当我正要对其中一个投反对票时,我看到了这些用户是谁,我想 h* 并停止了。

我会同时引用它们(然后请参阅原始答案以获取更多上下文)。

对于本 OP 段落:

我猜测文件读取操作有时不会在写入操作发生之前关闭文件[...]

回答说:

正确。文件系统不能很好地支持原子更新 [...] 使用 FileStream 无济于事 [...] 文件内部没有魔法。为了您的方便,它只是使用了 FileStream 包装。

但是我看不到任何原子操作(读取 + 后续写入)和 并行(由于部分重叠的多线程请求)可能会导致并发访问。即使是 atomic I/O 操作(读 + 写)也会有完全相同的问题。好的 FileStream 可能是异步的,但这不是 File.ReadAllText()File.WriteAllText() 使用它的方式。

另一个答案让我更加困惑,它说:

虽然根据文档,此方法保证文件句柄关闭,但即使引发异常,也不能保证关闭的时间在方法返回之前发生:关闭可以异步完成。

什么? MSDN 说方法将打开、读取和关闭文件(也在异常情况下)。这种方法是否有可能异步关闭文件?操作系统会推迟CloseHandle() 吗?在哪些情况下?为什么?

简而言之:这只是一个误解还是CloseHandle()是异步的?我错过了非常重要的东西?

【问题讨论】:

  • 我不同意该线程中的答案。当 Windows API 从 CloseHandle() 返回时,该句柄将关闭。如果有异常,那是因为有多个线程同时访问它。由于File.ReadAllLines() 最终使用CloseHandle() 关闭文件,因此文件将在返回时关闭。如果这不是真的,所有的地狱都会崩溃。我很确定另一个问题中提到的错误是由于多线程问题造成的。
  • @MatthewWatson 我同意你的观点,它会破坏我们一半的代码。我问只是因为那些用户代表(以防它是一个远程无证奇怪的角落案例)。
  • 事实上,如果您考虑一种用于日志文件(打开、追加、刷新、关闭)的常见模式,其中会发生非常频繁的更新,如果您不能保证可以重新打开,事情很快就会变得很糟糕关闭后不久的文件。 (可能存在涉及其他类型句柄的实例,但其工作方式不同,但对于打开单个实例的基本文件句柄,它们将确定性地关闭。)
  • @MatthewWatson 只要只有一个句柄,而且是同步的,你是对的。 FileStream 将只有一个句柄,所以这很好,它也将与 File.ReadXXX 同步。您提到的用于日志文件的通用模式 是错误的 (关闭之前的冗余刷新是一个小例子) - 它只是错误地不足以在任何时候完全失败。就可维护性而言,这是最糟糕的错误。也就是说,在这种情况下,问题出在其他地方 - 其他东西正在触及同一个文件。这在 ASP.NET 中非常很常见。
  • 文件系统是邪恶的,并且每时每刻都在密谋破坏您的代码。当面对文件系统逃跑时,像小女孩一样尖叫通常是最好的方法。

标签: c# .net io


【解决方案1】:

如果您查看CloseHandle 文档,它指出每个打开句柄的方法都有关于如何关闭它的描述:

创建这些对象的函数的文档 表示完成后应使用 CloseHandle 对象,以及之后对对象的待处理操作会发生什么 句柄已关闭。 通常,CloseHandle 会使 指定对象句柄,递减对象的句柄计数,以及 执行对象保留检查。在对象的最后一个句柄之后 已关闭,则从系统中删除该对象。

当您查看 CreateFile 文档时,它是这样说的:

当应用程序使用返回的对象句柄完成时 CreateFile,使用 CloseHandle 函数关闭句柄。 这不是 只释放系统资源,但可以对事物产生更广泛的影响 例如共享文件或设备以及将数据提交到磁盘。

我会发现 CloseHandle 会在异步保留文件以进行额外检查的同时关闭底层句柄是很奇怪的。这会削弱操作系统对调用者的许多保证,并且会成为许多错误的来源。

【讨论】:

  • 确实如此。以下代码可能会失败(除非其他线程正在访问同一个文件)的想法是荒谬的:while (true) { File.WriteAllText("test.txt", "1\n2\n3\n"); File.ReadAllText("test.txt"); }
【解决方案2】:

您问题中的前两个引号不应该相关。当File.* 完成时,或者当您关闭FileStream 时,文件会立即解锁。从来没有任何一种“缠绵”。如果有,您将永远无法在不重新启动的情况下再次安全地访问同一个文件。

May 的回答假定问题中的代码正在并行运行多次。如果不是,那么该代码显然是安全的。

但是我没有看到任何对原子操作的期望......即使是原子 I/O 操作(读 + 写)也会有完全相同的问题。

确实如此。我不知道为什么我在回答中对此发表了声明(虽然它是正确的。只是不相关)。

关闭的时间不保证在方法返回之前发生:关闭可以异步完成。

我不知道他为什么这么说,因为在我能想到的任何情况下都是不正确的。关闭句柄会立即生效。


我认为你对情况的理解是完全准确的。显然,我们的答案不明确,而且有点误导……对此感到抱歉。

【讨论】:

  • 关闭句柄会立即生效 - 只要该句柄只有一个用户。不过,File 上的静态方法总是如此(以及大多数其他托管 API)。
猜你喜欢
  • 1970-01-01
  • 2011-04-05
  • 2012-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多