【问题标题】:Is copying a file while writing to it thread safe?在写入文件时复制文件是线程安全的吗?
【发布时间】:2014-06-24 03:07:10
【问题描述】:

是否同时使用FileStream 类写入文件和.NET File.Copy 方法复制文件线程安全?操作系统似乎应该安全地处理对文件的并发访问,但我找不到任何关于此的文档。我编写了一个简单的应用程序进行测试,并且看到了奇怪的结果。该文件的副本显示为 2MB,但是当我使用 notepad++ 检查文件内容时,里面是空的。原始文件包含数据。

using System;
using System.Threading.Tasks;
using System.Threading;
using System.IO;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            string filePath = Environment.CurrentDirectory + @"\test.txt";
            using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
            {
                Task fileWriteTask = Task.Run(() =>
                    {
                        for (int i = 0; i < 10000000; i++)
                        {
                            fileStream.WriteByte((Byte)i);
                        }
                    });

                Thread.Sleep(50);
                File.Copy(filePath, filePath + ".copy", true);
                fileWriteTask.Wait();
            }
        }
    }
}

感谢您的帮助!

【问题讨论】:

  • 我不确定这个问题中是否有任何与 C# 相关的内容 - 这似乎更多地是关于 Windows API 如何处理共享读/写操作。
  • 旁注:用文本编辑器检查二进制文件的内容不是最好的检查,请确保以二进制模式打开。
  • 您要解决的更大问题是什么?很明显,您拥有的代码无法正常工作。
  • 请注意,除非文件的副本与原始文件位于不同的物理硬盘驱动器上,否则尝试同时写入两个文件将显着减少磁盘写入速度(因为它需要在每个文件的扇区之间移动磁盘头)并且由于这个过程几乎肯定会受到 IO 限制,而不是 CPU 限制,因此 CPU 并发不会为您带来任何好处。简而言之,您应该会看到 在此处仅使用一个线程,从而显着提高了速度
  • 我的建议是关闭这个问题并提出一个新问题,详细解释你想要做什么。

标签: c# .net multithreading


【解决方案1】:

视情况而定。

取决于你所说的“线程安全”是什么意思。

首先看这个构造函数:

public FileStream(string path, FileMode mode, FileAccess access, FileShare share )

注意最后一个参数,它说明您允许其他线程和进程对文件执行什么操作。适用于没有它的构造函数的默认值为FileShare.Read,这意味着您允许其他人以只读方式查看文件。如果您正在写信,这当然是不明智的。

这就是你基本上所做的,你打开一个文件进行写入,同时允许其他人读取它,而“读取”包括复制。

另外请注意,如果在代码末尾没有这个:fileWriteTask.Wait();,您的整个函数就不是线程安全的,因为 FileStream 可能在您开始编写之前就已关闭。

Windows 确实使文件访问线程安全,但以一种非常重要的方式。例如,如果您使用FileShare.None 打开文件,它会崩溃File.Copy,据我所知,使用.Net 并没有一种优雅的方式来做到这一点。 Windows 用来同步文件访问的一般方法称为乐观并发,意思是假设您的操作是可能的,如果不是,则失败。

this question discusses waiting for file lock in .Net

在进程之间共享文件是一个常见问题,其中一种方法,主要用于进程间通信是memory mapped files,这是the MSDN documentation

如果你有勇气并且愿意尝试使用 WinAPI 和 Overlapped IO,如果我没记错的话LockFileEx 允许很好的文件锁定...

此外,曾经有一个神奇的东西叫做Transactional NTFS,但它已经进入了微软弃用技术的领域

【讨论】:

    【解决方案2】:

    在“C# 对象都不会被破坏”的意义上,它是线程安全的。

    操作的结果或多或少是随机的(空文件、部分复制、拒绝访问),并且取决于每次操作打开文件时使用的共享模式。

    如果仔细设置,这可以产生合理的结果。 IE。在每行之后刷新文件并指定兼容的共享模式将允许合理地确保复制完整的行。

    【讨论】:

    • 您需要某种类型的调解器才能完成这项工作。即使您在每次写入后刷新,读取也可以读取一个块,然后在下一次写入之前读取下一个块。然后它会看到文件结尾,然后退出。
    • @JimMischel - 我的建议是获得一些看起来可以消耗的东西(即日志文件),但实际上如果想要获得完整的文件方式,则需要编写更多代码以允许并行写入和复制...... IE。可以查看tee 的变体之一的源代码,以了解如何在运行时进行克隆。
    【解决方案3】:

    答案是否定的。通常,您无法对来自不同线程的文件系统对象进行操作并获得一致或可预测的文件内容结果。

    单独的 .NET Framework 函数可能是安全的,也可能不是安全的,但这无关紧要。从磁盘上的单个文件读取、写入或复制数据的时间和顺序本质上是不确定的。我的意思是,如果您多次执行相同的操作,您将获得不同的结果,具体取决于您无法控制的因素,例如机器负载和磁盘布局。

    情况变得更糟,因为负责 File.Copy 的 Windows API 在系统进程上运行,并且仅与您的程序松散同步。

    底线是,如果您想要文件级同步,您别无选择,只能使用文件级原语来实现它。这意味着诸如打开/关闭、冲洗、锁定之类的事情。找到有效的组合并非易事。

    一般来说,最好将文件上的所有操作保存在一个线程中,并同步对该线程的访问。


    在回答评论时,如果您通过使文件进行内存映射来操作文件,则在关闭文件之前,不能保证内存中的内容与磁盘上的内容一致。内存中的内容可以在进程或线程之间同步,但磁盘上的内容不能。

    命名互斥锁在进程之间锁定,但不保证文件系统对象的一致性。

    文件系统锁是我提到的可用于确保文件系统一致性的方法之一,但在许多情况下仍然无法保证。您依靠操作系统来使缓存的磁盘内容无效并刷新到磁盘,这并不能保证始终适用于所有文件。例如,可能需要使用 FILE_FLAG_NO_BUFFERING、FILE_FLAG_OVERLAPPED 和 FILE_FLAG_WRITE_THROUGH 标志,这可能会严重影响性能。

    如果有人认为这是一个简单的一刀切解决方案是一个简单的问题,那么他们根本就没有尝试过让它在实践中发挥作用。

    【讨论】:

    • -1 当然可以。它通常使用内存映射文件完成。您可以在 Windows 上锁定文件,如果您需要更好的同步,您可以使用命名互斥锁
    • 如果你认为这是一个答案,我认为你没有理解这个问题。见编辑。
    猜你喜欢
    • 2015-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-22
    • 1970-01-01
    • 1970-01-01
    • 2020-04-29
    • 1970-01-01
    相关资源
    最近更新 更多