并发File.Move的相同文件

时间:2015-07-08 20:51:35

标签: c# .net file atomic

有人明确表示File.Move是原子操作:Atomicity of File.Move

但是,以下代码段会导致多次移动同一文件的可见性

有谁知道这段代码有什么问题?

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace FileMoveTest
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = "test/" + Guid.NewGuid().ToString();

            CreateFile(path, new string('a', 10 * 1024 * 1024));

            var tasks = new List<Task>();

            for (int i = 0; i < 10; i++)
            {
                var task = Task.Factory.StartNew(() =>
                {
                    try
                    {
                        string newPath = path + "." + Guid.NewGuid();

                        File.Move(path, newPath);

                        // this line does NOT solve the issue
                        if (File.Exists(newPath))
                            Console.WriteLine(string.Format("Moved {0} -> {1}", path, newPath));
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(string.Format("  {0}: {1}", e.GetType(), e.Message));
                    }
                });

                tasks.Add(task);
            }

            Task.WaitAll(tasks.ToArray());
        }

        static void CreateFile(string path, string content)
        {
            string dir = Path.GetDirectoryName(path);

            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }

            using (FileStream f = new FileStream(path, FileMode.OpenOrCreate))
            {
                using (StreamWriter w = new StreamWriter(f))
                {
                    w.Write(content);
                }
            }
        }
    }
}

矛盾的输出如下。似乎该文件已多次移动到不同位置。在磁盘上只有一个存在。有什么想法吗?

Moved test/eb85560d-8c13-41c1-926a-6871be030742 -> test/eb85560d-8c13-41c1-926a-6871be030742.0018d317-ed7c-4732-92ac-3bb974d29017
Moved test/eb85560d-8c13-41c1-926a-6871be030742 -> test/eb85560d-8c13-41c1-926a-6871be030742.3965dc15-7ef9-4f36-bdb7-94a5939b17db
Moved test/eb85560d-8c13-41c1-926a-6871be030742 -> test/eb85560d-8c13-41c1-926a-6871be030742.fb66306a-5a13-4f26-ade2-acff3fb896be
Moved test/eb85560d-8c13-41c1-926a-6871be030742 -> test/eb85560d-8c13-41c1-926a-6871be030742.c6de8827-aa46-48c1-b036-ad4bf79eb8a9
System.IO.FileNotFoundException: Could not find file 'C:\file-move-test\test\eb85560d-8c13-41c1-926a-6871be030742'.
System.IO.FileNotFoundException: Could not find file 'C:\file-move-test\test\eb85560d-8c13-41c1-926a-6871be030742'.
System.IO.FileNotFoundException: Could not find file 'C:\file-move-test\test\eb85560d-8c13-41c1-926a-6871be030742'.
System.IO.FileNotFoundException: Could not find file 'C:\file-move-test\test\eb85560d-8c13-41c1-926a-6871be030742'.
System.IO.FileNotFoundException: Could not find file 'C:\file-move-test\test\eb85560d-8c13-41c1-926a-6871be030742'.
System.IO.FileNotFoundException: Could not find file 'C:\file-move-test\test\eb85560d-8c13-41c1-926a-6871be030742'.

生成的文件位于:

eb85560d-8c13-41c1-926a-6871be030742.fb66306a-5a13-4f26-ade2-acff3fb896be

更新。我可以确认,检查File.Exists也会 NOT 解决问题 - 它可以报告单个文件确实被移动到几个不同的位置。

即可。我最终得到的解决方案如下:在使用源文件操作之前创建特殊的“锁定”文件,如果成功,那么我们可以确定只有这个线程获得对文件的独占访问权限,我们可以安全地做任何我们想要的事情。以下是创建suck“lock”文件的正确参数集。

File.Open(lockPath, FileMode.CreateNew, FileAccess.Write);

1 个答案:

答案 0 :(得分:4)

  

有谁知道这段代码有什么问题?

我想这取决于你所说的“错误”。

您所看到的行为并非恕我直言,至少如果您使用的是NTFS(其他文件系统的行为可能相同或不同)。

底层OS API(MoveFile()MoveFileEx()函数)的文档并不具体,但通常API是线程安全的,因为它们保证了文件系统不会被并发操作破坏(当然,您自己的数据可能已损坏,但它将以文件系统一致的方式完成)。

最有可能发生的事情是,当移动文件操作继续进行时,首先从给定目录链接获取实际文件句柄(在NTFS中,您看到的所有“文件名”实际上都很难)链接到底层文件对象)。获得该文件句柄后,API然后为底层文件对象创建一个新文件名(即作为硬链接),然后删除以前的硬链接。

当然,随着这种情况的发展,在获得底层文件句柄的线程之间但在删除原始硬链接之前的时间内存在一个窗口。这允许一些但不是所有其他并发移动操作看起来成功。即最终原始的硬链接不存在,进一步尝试移动它将不会成功。

毫无疑问,上述内容过于简单化了。文件系统行为可能很复杂。特别是,您所陈述的观察结果是,只有在完成所有操作后才能使用该文件的单个实例。这表明API也以某种方式协调各种操作,这样只有一个新创建的硬链接幸存,可能是因为API实际上只是在检索文件对象句柄后重命名相关的硬链接,而不是创建一个新的并删除旧的(实现细节)。


在一天结束时,代码的“错误”在于它故意尝试在单个文件上执行并发操作。虽然文件系统本身将确保它保持连贯,但是由您自己的代码来确保这些操作是协调的,以便结果可预测和可靠。