使用Directory.Delete()和Directory.CreateDirectory()覆盖文件夹

时间:2015-09-15 18:55:53

标签: c# asp.net-web-api asp.net-web-api2

在我的WebApi操作方法中,我想使用以下代码创建/覆盖文件夹:

string myDir = "...";
if(Directory.Exists(myDir)) 
{
    Directory.Delete(myDir, true);
}
Directory.CreateDirectory(myDir);

// 1 - Check the dir 
Debug.WriteLine("Double check if the Dir is created: " + Directory.Exists(myDir));

// Some other stuff here...

// 2 - Check the dir again
Debug.WriteLine("Check again if the Dir still exists: " + Directory.Exists(myDir));

问题

奇怪的是,有时在创建目录后,该目录不存在!

有时第一次检查dir时(数字1的位置); Directory.Exist()返回true,其他时间false。第二次检查目录时(数字2的位置)也是如此。

备注

  • 这部分代码都没有抛出任何异常。
  • 只有在服务器上发布网站时才能重现这一点。 (Windows server 2008)
  • 访问同一文件夹时发生。

问题

  • 这是并发问题竞争条件吗?
  • WebApi或操作系统是否处理并发?
  • 这是覆盖文件夹的正确方法吗?
  • 当我们对同一个文件有很多API请求时,我是否应该手动锁定文件?

或者一般情况:

  • 这种奇怪行为的原因是什么?

更新

  • 使用DirectoryInfoRefresh()代替Directory无法解决问题。

  • 仅当删除递归选项为true时才会发生。 (并且目录不为空)。

3 个答案:

答案 0 :(得分:10)

许多文件系统操作在某些文件系统上不同步(在Windows的情况下 - NTFS)。以RemoveDirectory调用为例(在某些时候由Directory.DeleteDirectory调用):

  

RemoveDirectory函数在关闭时标记要删除的目录。因此,在关闭目录的最后一个句柄之前,不会删除该目录。

如您所见,它将不会真正删除目录,直到它的所有句柄都关闭,但Directory.DeleteDirectory将完成。在您的情况下,这也很可能是这样的并发问题 - 在您执行Directory.Exists时,并未真正创建目录。

因此,只需定期检查您的需求,不要将.NET中的文件系统调用视为同步。在某些情况下,您还可以使用FileSystemWatcher来避免轮询。

编辑:我在想如何重现它,这是代码:

internal class Program {
    private static void Main(string[] args) {
        const string path = "G:\\test_dir";
        while (true) {         
            if (Directory.Exists(path))
                Directory.Delete(path);       
            Directory.CreateDirectory(path);   
            if (!Directory.Exists(path))
                throw new Exception("Confirmed");                 
        }            
    }        
}

您会看到,如果所有文件系统调用都是同步的(在.NET中),则此代码应该没有问题。现在,在运行该代码之前,在指定路径创建空目录(最好不要使用SSD)并使用Windows资源管理器打开它。现在运行代码。对我来说,它会抛出Confirmed(它完全重现你的问题)或抛出Directory.Delete说该目录不存在(几乎相同的情况)。它100%的时间都是我。

这是另一个代码,当我在我的机器上运行时,确认File.Exists肯定可以在File.Delete调用之后直接返回true:

internal class Program {
    private static void Main(string[] args) {
        while (true) {
            const string path = @"G:\test_dir\test.txt";
            if (File.Exists(path))
                File.Delete(path);
            if (File.Exists(path))
                throw new Exception("Confirmed");
            File.Create(path).Dispose();
        }
    }        
 }

exception

为此,我打开了G:\ test_dir文件夹,在执行此代码时尝试打开不断出现和消失的test.txt文件。经过几次尝试,抛出了确认异常(虽然我没有创建或删除该文件,但在抛出异常后,它已经不存在于文件系统中)。因此,在多种情况下,竞争条件是可能的,我的答案是正确的。

答案 1 :(得分:1)

我为自己编写了一些C#方法,用于使用Directory.Delete()进行同步文件夹删除。随时复制:

private bool DeleteDirectorySync(string directory, int timeoutInMilliseconds = 5000)
{
    if (!Directory.Exists(directory))
    {
        return true;
    }

    var watcher = new FileSystemWatcher
    {
        Path = Path.Combine(directory, ".."),
        NotifyFilter = NotifyFilters.DirectoryName,
        Filter = directory,
    };
    var task = Task.Run(() => watcher.WaitForChanged(WatcherChangeTypes.Deleted, timeoutInMilliseconds));

    // we must not start deleting before the watcher is running
    while (task.Status != TaskStatus.Running)
    {
        Thread.Sleep(100);
    }

    try
    {
        Directory.Delete(directory, true);
    }
    catch
    {
        return false;
    }

    return !task.Result.TimedOut;
}

请注意,获取task.Result将阻塞线程,直到任务完成为止,从而使该线程的CPU负载保持空闲状态。所以这就是同步的地方。

答案 2 :(得分:-2)

听起来像我的竞争条件。不确定为什么 - 你没有提供足够的细节 - 但你可以做的是将所有内容包装在lock()语句中,看看问题是否已经消失。当然,这不是生产就绪的解决方案,它只是一种快速检查方式。如果它确实是竞争条件 - 您需要重新考虑重写文件夹的方法。可能是创造" GUID"文件夹和完成后 - 使用最新的GUID更新DB以指向最新的文件夹?..