使用 Directory.Delete() 和 Directory.CreateDirectory() 覆盖文件夹
Using Directory.Delete() and Directory.CreateDirectory() to overwrite a folder
在我的 WebApi
操作方法中,我想 create/over-write 使用此代码的文件夹:
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));
问题
奇怪的是,有时创建目录后,目录不存在!
有时在第一次检查目录时(数字1所在的位置); Directory.Exist()
returns true
,其他时间 false
。第二次检查目录(数字 2 所在的位置)时也会发生同样的情况。
备注
- None 这部分代码抛出任何异常。
- 只有在服务器上发布网站时才能重现。 (Windows 服务器 2008)
- 访问同一文件夹时发生。
问题
- 这是并发问题竞争条件吗?
WebApi
或操作系统不处理并发吗?
- 这是覆盖文件夹的正确方法吗?
- 当我们对同一个文件有很多 API 请求时,我应该手动锁定文件吗?
或者一般来说:
- 这种奇怪行为的原因是什么?
更新:
使用DirectoryInfo
和Refresh()
代替Directory
并不能解决问题。
仅当 Delete 的 recursive 选项为 true
时才会发生。 (并且目录不为空)。
对我来说听起来像是竞争条件。不知道为什么 - 你没有提供足够的细节 - 但你可以做的是将所有内容包装在 lock() 语句中,看看问题是否消失了。可以肯定的是,这不是生产就绪的解决方案,它只是一种快速检查的方法。如果它确实是一个竞争条件 - 你需要重新考虑你重写文件夹的方法。可能会创建 "GUID" 文件夹,完成后 - 使用最新的 GUID 更新数据库以指向最新的文件夹?..
许多文件系统操作在某些文件系统上不同步(在 windows - NTFS 的情况下)。以 RemoveDirectory 调用为例(在某些时候由 Directory.DeleteDirectory 调用):
The RemoveDirectory function marks a directory for deletion on close. Therefore, the directory is not removed until the last handle to the directory is closed.
如您所见,它不会真正删除目录,直到它的所有句柄都关闭,但 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% 的时间都在做。
这是另一个代码,当 运行ning 在我的机器上确认肯定有可能 File.Exists 在 File.Delete 调用之后直接 return 为真:
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();
}
}
}
为此,我打开了 G:\test_dir 文件夹,并在执行此代码期间尝试打开不断出现和消失的 test.txt 文件。几次尝试后,抛出了 Confirmed 异常(虽然我没有创建或删除该文件,并且在抛出异常之后,它已经不存在于文件系统中)。所以竞争条件在多种情况下都是可能的,我的回答是正确的。
我自己写了一个使用Directory.Delete()
同步文件夹删除的C#小方法。欢迎复制:
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负载空闲。这就是它同步的地方。
在我的 WebApi
操作方法中,我想 create/over-write 使用此代码的文件夹:
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));
问题
奇怪的是,有时创建目录后,目录不存在!
有时在第一次检查目录时(数字1所在的位置); Directory.Exist()
returns true
,其他时间 false
。第二次检查目录(数字 2 所在的位置)时也会发生同样的情况。
备注
- None 这部分代码抛出任何异常。
- 只有在服务器上发布网站时才能重现。 (Windows 服务器 2008)
- 访问同一文件夹时发生。
问题
- 这是并发问题竞争条件吗?
WebApi
或操作系统不处理并发吗?- 这是覆盖文件夹的正确方法吗?
- 当我们对同一个文件有很多 API 请求时,我应该手动锁定文件吗?
或者一般来说:
- 这种奇怪行为的原因是什么?
更新:
使用
DirectoryInfo
和Refresh()
代替Directory
并不能解决问题。仅当 Delete 的 recursive 选项为
true
时才会发生。 (并且目录不为空)。
对我来说听起来像是竞争条件。不知道为什么 - 你没有提供足够的细节 - 但你可以做的是将所有内容包装在 lock() 语句中,看看问题是否消失了。可以肯定的是,这不是生产就绪的解决方案,它只是一种快速检查的方法。如果它确实是一个竞争条件 - 你需要重新考虑你重写文件夹的方法。可能会创建 "GUID" 文件夹,完成后 - 使用最新的 GUID 更新数据库以指向最新的文件夹?..
许多文件系统操作在某些文件系统上不同步(在 windows - NTFS 的情况下)。以 RemoveDirectory 调用为例(在某些时候由 Directory.DeleteDirectory 调用):
The RemoveDirectory function marks a directory for deletion on close. Therefore, the directory is not removed until the last handle to the directory is closed.
如您所见,它不会真正删除目录,直到它的所有句柄都关闭,但 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% 的时间都在做。
这是另一个代码,当 运行ning 在我的机器上确认肯定有可能 File.Exists 在 File.Delete 调用之后直接 return 为真:
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();
}
}
}
为此,我打开了 G:\test_dir 文件夹,并在执行此代码期间尝试打开不断出现和消失的 test.txt 文件。几次尝试后,抛出了 Confirmed 异常(虽然我没有创建或删除该文件,并且在抛出异常之后,它已经不存在于文件系统中)。所以竞争条件在多种情况下都是可能的,我的回答是正确的。
我自己写了一个使用Directory.Delete()
同步文件夹删除的C#小方法。欢迎复制:
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负载空闲。这就是它同步的地方。