【问题标题】:File.WriteAllBytes does not blockFile.WriteAllBytes 不阻塞
【发布时间】:2013-05-20 17:41:46
【问题描述】:

我有一段简单的代码,如下所示:

File.WriteAllBytes(Path.Combine(temp, node.Name), stuffFile.Read(0, node.FileHeader.FileSize));

有人会认为WriteAllBytes 将是一个阻塞调用,因为它在 C# 5.0 中具有异步对应项,并且它在任何 MSDN 文档中都没有说明它是非阻塞的。但是,当文件大小合理(不是很大,但在 20mb 范围内)时,之后打开文件的调用似乎在写入完成之前被调用,并且文件被打开(程序抱怨它已损坏,没错),然后WriteAllBytes 抱怨文件在另一个进程中打开。这里发生了什么?!出于好奇,这是用于打开文件的代码:

System.Diagnostics.Process.Start(Path.Combine(temp, node.Name));

有没有人经历过这种奇怪的事情?还是因为我是金发女郎而做错了什么?

如果确实阻塞,可能是什么原因导致了这个问题?

编辑:我会提出完整的方法。

var node = item.Tag as FileNode;
stuffFile.Position = node.FileOffset;
string temp = Path.GetTempPath();
File.WriteAllBytes(Path.Combine(temp, node.Name), stuffFile.Read(0, node.FileHeader.FileSize));
System.Diagnostics.Process.Start(Path.Combine(temp, node.Name));

似乎正在发生的事情是 Process.StartWriteAllBytes 完成之前被调用,它试图打开文件,然后 WriteAllBytes 抱怨另一个进程持有文件的锁。

【问题讨论】:

  • WriteAllBytes 在执行后如何抱怨?您应该在调试器中单步执行以查看实际情况。
  • 嗯,这就是我的想法。我会用整个方法更新问题,也许这会有所帮助。
  • 如果Process.Start 已经在执行,WriteAllBytes 就无法抱怨。即使WriteAllBytes 是非阻塞的(它不是),它仍然会在执行下一行代码之前返回,并且一旦函数返回它就无法抱怨。
  • 我对 File.WriteAllBytes 有同样的问题。当我用这个函数写一个文件时,它告诉我,这个过程已经完成。但是,如果另一个应用程序在我的应用程序完成后尝试读取此文件 - 文件似乎不存在。如果我调试,文件有足够的时间“出现”,所以在调试时这个工作流程是有效的。如果我在两者之间添加超过 2 秒的延迟,它也可以工作,这是无稽之谈(而且我正在使用 SSD !!!)。我的猜测是,Windows 可以为“更好的体验”进行一些“优化”。

标签: c# .net file-io


【解决方案1】:

不,WriteAllBytes 是一种阻塞的同步方法。正如您所说,如果不是,文档会这样说。

可能病毒扫描程序仍在忙于扫描您刚刚编写的文件,并负责锁定该文件。尝试暂时禁用扫描仪以检验我的假设。

【讨论】:

  • 它不是可执行文件,我正在使用 Process.Start 调用 Windows 中设置的默认应用程序来打开文件。这绝对不是防病毒软件,因为很明显文件没有完成写入,因为例如 Word 抱怨文件已损坏。我怀疑病毒扫描程序甚至会中断 WriteAllBytes 调用,因为它似乎做的是创建一个文件,然后写入所有字节,然后关闭流。在 .NET 完成写入并关闭流之前,我认为 AVG 无法获取文件流?
  • 不要挂断我对根本原因的猜测。您询问 WriteAllBytes 是否是同步的。答案是肯定的。
  • 那么,我们是否必须解决整个难题,这依赖于我们没有的信息?回答关于 WriteAllBytes 是否阻塞的原始问题还不够吗?如果您希望人们调试您的代码,那么为什么要隐藏它呢?准备一个简短但完整的程序来演示该问题。以便它可以被复制。就目前而言,您提出了一个可以回答的简单问题,但现在显示出将其转变为变色龙问题的迹象。
  • 好吧,如果有一些正确方向的提示就好了。如果您知道任何可能导致此问题的内容,那就太好了,否则我会将这个问题标记为正确并继续尝试自己解决。
  • 我给出了最明显的提示。如果您希望我们为您的特定程序提供帮助,我们需要它。清楚地削减到最低限度。
【解决方案2】:

我认为您的问题可能与您从文件中读取的方式有关。请注意,Stream.Read(和 FileStream.Read不需要阅读您的所有请求

换句话说,您的调用 stuffFile.Read(0, node.FileHeader.FileSize) 可能(有时肯定会)返回一个 node.FileHeader.FileSize 数组,其中包含文件开头的一些字节,然后是 0。

错误在您的UsableFileStream.Read 方法中。您可以通过将整个文件读入内存来修复它:

    public byte[] Read(int offset, int count)
    {
        // There are still bugs in this method, like assuming that 'count' bytes
        // can actually be read from the file
        byte[] temp = new byte[count];

        int bytesRead;
        while ( count > 0 && (bytesRead = _stream.Read(temp, offset, count)) > 0 )
        {
            offset += bytesRead;
            count -= bytesRead;
        }

        return temp;
    }

但由于您仅使用它来复制文件内容,您可以避免这些潜在的大量分配并在您的tree_MouseDoubleClick 中使用Stream.CopyTo

var node = item.Tag as FileNode;
stuffFile.Position = node.FileOffset;
string temp = Path.GetTempPath();
using (var output = File.Create(Path.Combine(temp, node.Name)))
    stuffFile._stream.CopyTo(output);

System.Diagnostics.Process.Start(Path.Combine(temp, node.Name));

【讨论】:

  • 谢谢,我去看看。整个文件没有加载到内存中的原因是因为它们有时是 300mb 或更大。该文件被索引(例如,文件中的每个文件都在实际数据之前存储其信息,因此您可以在需要时轻松“读取”您需要的文件部分,而无需将整个文件加载到内存中)。格式的规范说那里总是有正确的字节,即使它们被填充。我会检查 :) 谢谢你的回答。
【解决方案3】:

有点晚了,但为了其他可能出现的人的利益而添加。

File.WriteAllBytes 的底层 C# 实现很可能是同步的,但 C# 的作者无法在操作系统级别控制如何处理写入磁盘的操作。

所谓的写入缓存意味着当 C# 要求将文件保存到磁盘时,操作系统可能会在文件完全写入磁盘之前返回“I'm done”,从而导致问题 OP 突出显示。

那样的话,写完之后,最好是循环休眠,在调用Process.Start之前一直检查文件是否仍然被锁定。

你可以看到我在这里遇到了由此引起的问题:C#, Entity Framework Core & PostgreSql : inserting a single row takes 20+ seconds

另外,在 OPs 帖子的最后一句“然后 WriteAllBytes 抱怨另一个进程持有文件的锁”。我认为他们实际上是想写“然后 Process.Start 抱怨”,这似乎在 cmets 中引起了一些混乱。

【讨论】:

    猜你喜欢
    • 2014-05-25
    • 1970-01-01
    • 2012-12-11
    • 1970-01-01
    • 1970-01-01
    • 2012-02-20
    • 1970-01-01
    • 2016-07-06
    • 1970-01-01
    相关资源
    最近更新 更多