同一文件的并发File.Move [英] Concurrent File.Move of the same file

查看:153
本文介绍了同一文件的并发File.Move的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有人明确指出,File.Move是原子操作位置:原子File.Move的

但接下来的code片段结果的知名度移动同一个文件多次的

有谁知道什么是不对的code?

 使用系统;
使用System.Collections.Generic;
使用System.IO;
使用System.Threading.Tasks;

命名空间FileMoveTest
{
    类节目
    {
        静态无效的主要(字串[] args)
        {
            。字符串路径=测试/+ Guid.NewGuid()的ToString();

            的CreateFile(路径,新的字符串(A,10 * 1024 * 1024));

            VAR任务=新的名单,其中,任务>();

            的for(int i = 0;我小于10;我++)
            {
                VAR任务= Task.Factory.StartNew(()=>
                {
                    尝试
                    {
                        字符串的新路径=路径+。 + Guid.NewGuid();

                        File.Move(路径的新路径);

                        //这行不解决问题
                        如果(File.Exists(的新路径))
                            Console.WriteLine(的String.Format(感动{0}  - > {1},路径的新路径));
                    }
                    赶上(例外五)
                    {
                        Console.WriteLine(的String.Format({0}:{1},e.GetType(),e.​​Message));
                    }
                });

                tasks.Add(任务);
            }

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

        静态无效的CreateFile(路径字符串,字符串内容)
        {
            字符串DIR = Path.GetDirectoryName(路径);

            如果(!Directory.Exists(DIR))
            {
                Directory.CreateDirectory(DIR);
            }

            使用(的FileStream F =新的FileStream(路径,FileMode.OpenOrCreate))
            {
                使用(StreamWriter的W =新的StreamWriter(F))
                {
                    w.Write(内容);
                }
            }
        }
    }
}
 

自相矛盾的输出如下。看来,文件被移动多次到不同的位置。在盘仅其中之一是present。有什么想法?

感动测试/ eb85560d-8c13-41c1-926a-6871be030742  - >测试/ eb85560d-8c13-41c1-926a-6871be030742.0018d317-ed7c-4732-92ac-3bb974d29017
感动测试/ eb85560d-8c13-41c1-926a-6871be030742  - >测试/ eb85560d-8c13-41c1-926a-6871be030742.3965dc15-7ef9-4f36-bdb7-94a5939b17db
感动测试/ eb85560d-8c13-41c1-926a-6871be030742  - >测试/ eb85560d-8c13-41c1-926a-6871be030742.fb66306a-5a13-4f26-ADE2,acff3fb896be
感动测试/ eb85560d-8c13-41c1-926a-6871be030742  - >测试/ eb85560d-8c13-41c1-926a-6871be030742.c6de8827-aa46-48c1-B036-ad4bf79eb8a9
System.IO.FileNotFoundException:找不到找不到文件C:\文件移动测试\测试\ eb85560d-8c13-41c1-926a-6871be030742。
System.IO.FileNotFoundException:找不到找不到文件C:\文件移动测试\测试\ eb85560d-8c13-41c1-926a-6871be030742。
System.IO.FileNotFoundException:找不到找不到文件C:\文件移动测试\测试\ eb85560d-8c13-41c1-926a-6871be030742。
System.IO.FileNotFoundException:找不到找不到文件C:\文件移动测试\测试\ eb85560d-8c13-41c1-926a-6871be030742。
System.IO.FileNotFoundException:找不到找不到文件C:\文件移动测试\测试\ eb85560d-8c13-41c1-926a-6871be030742。
System.IO.FileNotFoundException:找不到找不到文件C:\文件移动测试\测试\ eb85560d-8c13-41c1-926a-6871be030742。

生成的文件是在这里:

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

更新我可以证实,检查File.Exists也做不会解决问题 - 它可以报告说,单个文件是真的感动到几个不同的位置

解决方案:。我最终的解决方案是以下几点:在此之前与源文件操作产生特殊的锁定的文件,如果成功那么我们可以肯定,只有这个线程获得独占访问该文件,我们是安全的,做任何事情,我们想要的。下面是正确的参数设置来创建吸锁定的文件。

  File.open方法(lockPath,FileMode.CreateNew,FileAccess.Write);
 

解决方案
  

有谁知道什么是不对的code?

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

意思

您所看到的行为不恕我直言意外,至少如果你使用的是NTFS(其他的文件系统可能会或可能不会表现相似)。

本文档中的底层操作系统API(的 MoveFile()和的 MoveFileEx()的函数)是不特定,但在一般的API是线程安全的,因为它们保证的文件系统的将不被并发操作损坏(当然,自己的数据可能被破坏,但它会在一个文件系统一致的方式来完成)。

最有可能正在发生的事情是,作为移动文件操作进行时,它会通过首先获得从给定的目录链接的实际文件句柄到它(在NTFS,所有的文件名,你看其实很难链接到底层的文件对象)。在获得该文件句柄,然后,API为基础的文件对象(即作为一个硬连接)创建一个新的文件名,然后删除previous硬链接。

当然,作为本的进展,存在已经获得的基础文件句柄中一个线程之间的时间的窗口但原始硬链接之前已经被删除。这允许一些但不是所有的其他并发移动操作的出现才能成功。即最终原来的硬链接不存在,并进一步尝试将不会成功。

毫无疑问,上面是一个过于简单化。文件系统的行为可能是复杂的。特别是,你所述的观察是,你只风与文件的单个实例,当一切都说过和做过。这表明,API并还以某种方式协调各种操作,使得只有一个新创建硬链接的生存,可能是凭借该API实际上只是重命名检索所述文件对象句柄后相关的硬链接,而不是创建一个新的,删除旧的(实现细节)。


在一天结束时,什么是错误的与code是它故意试图在单个文件上执行并行操作。虽然文件系统本身将确保它保持一致,就看你自己的code,以确保此类操作协调,以便结果是predictable可靠。

It was clearly stated that File.Move is atomic operation here: Atomicity of File.Move.

But the following code snippet results in visibility of moving the same file multiple times.

Does anyone know what is wrong with this code?

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);
                }
            }
        }
    }
}

The paradoxical output is below. Seems that file was moved multiple times onto different locations. On the disk only one of them is present. Any thoughts?

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'.

The resulting file is here:

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

UPDATE. I can confirm that checking File.Exists also does NOT solve the issue - it can report that single file was really moved into several different locations.

SOLUTION. The solution I end up with is following: Prior to operations with source file create special "lock" file, if it succeeded then we can be sure that only this thread got exclusive access to the file and we are safe to do anything we want. The below is right set of parameters to create suck "lock" file.

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

解决方案

Does anyone know what is wrong with this code?

I guess that depends on what you mean by "wrong".

The behavior you're seeing is not IMHO unexpected, at least if you're using NTFS (other file systems may or may not behave similarly).

The documentation for the underlying OS API (MoveFile() and MoveFileEx() functions) is not specific, but in general the APIs are thread-safe, in that they guarantee the file system will not be corrupted by concurrent operations (of course, your own data could be corrupted, but it will be done in a file-system-coherent way).

Most likely what is occurring is that as the move-file operation proceeds, it does so by first getting the actual file handle from the given directory link to it (in NTFS, all "file names" that you see are actually hard links to an underlying file object). Having obtained that file handle, the API then creates a new file name for the underlying file object (i.e. as a hard link), and then deletes the previous hard link.

Of course, as this progresses, there is a window during the time between a thread having obtained the underlying file handle but before the original hard link has been deleted. This allows some but not all of the other concurrent move operations to appear to succeed. I.e. eventually the original hard link doesn't exist and further attempts to move it won't succeed.

No doubt the above is an oversimplification. File system behaviors can be complex. In particular, your stated observation is that you only wind up with a single instance of the file when all is said and done. This suggests that the API does also somehow coordinate the various operations, such that only one of the newly-created hard links survives, probably by virtue of the API actually just renaming the associated hard link after retrieving the file object handle, as opposed to creating a new one and deleting the old one (implementation detail).


At the end of the day, what's "wrong" with the code is that it is intentionally attempting to perform concurrent operations on a single file. While the file system itself will ensure that it remains coherent, it's up to your own code to ensure that such operations are coordinated so that the results are predictable and reliable.

这篇关于同一文件的并发File.Move的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆