Использование Directory.Delete() и Directory.CreateDirectory() для перезаписи папки
В моем 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 к одному и тому же файлу?
Или вообще:
- В чем причина этого странного поведения?
ОБНОВИТЬ:
С помощью
DirectoryInfo
а такжеRefresh()
вместоDirectory
не решает проблему.Это происходит только тогда, когда рекурсивная опция Delete
true
, (и каталог не пустой).
2 ответа
Многие операции с файловой системой не являются синхронными в некоторых файловых системах (в случае 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. Теперь запустите код. Для меня это либо выбрасывает Подтверждено (что точно воспроизводит вашу проблему), либо выдает на Directory.Delete, говоря, что каталог не существует (почти тот же случай). Это делает это 100% времени для меня.
Вот еще один код, который при запуске на моем компьютере подтверждает, что File.Exists, безусловно, может возвращать true сразу после вызова File.Delete:
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. После нескольких попыток выдается подтвержденное исключение (хотя я не создавал и не удалял этот файл, а после создания исключения его уже нет в файловой системе). Таким образом, условия гонки возможны во многих случаях, и мой ответ правильный.
Я написал себе небольшой метод 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
будет блокировать поток до тех пор, пока задача не будет завершена, сохраняя загрузку процессора этого потока в режиме ожидания. Так что это момент, когда он становится синхронным.
Для меня это похоже на состояние гонки. Не уверен, почему - вы не предоставили достаточно подробностей - но что вы можете сделать, это заключить все в оператор lock() и посмотреть, исчезла ли проблема. Конечно, это не готовое решение, это только быстрый способ проверить. Если это действительно состояние гонки - вам нужно переосмыслить свой подход переписывания папок. Может быть, создайте папку "GUID", а когда закончите - обновите БД с самым последним GUID, чтобы он указывал на самую последнюю папку?..