Создать каталог, только если он не существует (.NET)

Есть ли способ убедиться, что именно я создал каталог на C#?

По умолчанию Directory.CreateDirectory по сути является операцией UPSERT: если каталог не существует, он создается, но если он уже существует, то это нормально. Пока он существует по возвращении, все счастливы.

Но я намерен удалить поддерево, когда закончу с ним, и не хочу этого делать, если я не создал его изначально. Насколько я могу судить, единственным способом добиться этого будет P/Invoke, потому что с управляемым API я должен проверять существование отдельно, и между Directory.Exists и Directory.CreateDirectory всегда будет состояние гонки.

Однако в API базовой платформы вызов создания каталога вернет ошибку, если он уже существует (ERROR_ALREADY_EXISTS из CreateDirectoryW в Windows, EEXIST из mkdir в системах POSIX).

Есть ли управляемый способ создания каталога, но только если он не существует в .NET?

Эти методы пытаются создать весь путь, вам это нужно? Вы упоминаете POSIX, поэтому пытаетесь или должны оставаться независимыми от системы?

Ralf 02.07.2024 20:46

Для окон посмотрите github.com/dotnet/runtime/blob/… и используйте только те части, которые вам нужны, и проверьте, не являются ли другие вещи, которые вы там видите, неуместными;)

Ralf 02.07.2024 20:48

О, я могу без проблем выполнить P/Invoke, просто предпочитаю этого не делать, если в этом нет необходимости. Но, похоже, в этом случае мне, возможно, придется. Вздох. :-П

Jonathan Gilbert 02.07.2024 20:54

Я связал источник, потому что это может быть нечто большее, чем просто вызов Win API. Например, если вы будете следовать «Interop.Kernel32.CreateDirectory», вы увидите не только вызов pinvoke.

Ralf 02.07.2024 21:00

Спасибо :-) Я много работал над разработкой Win32 на C/C++, а также на C#, так что вызвать эту функцию не составляет большого труда. Я просто не хочу создавать эту зависимость, если есть управляемый способ сделать это. Хотя в данном случае я его не видел. :-П

Jonathan Gilbert 02.07.2024 21:07

с управляемым API я должен проверять существование отдельно, и между _Directory.Exists и Directory.CreateDirectory всегда будет состояние гонки? К сожалению, этот пост не имеет особого смысла. Почему вы считаете, что существует состояние гонки?

user246821 02.07.2024 21:09

Мой процесс: do { index++; путь = "/tmp/foobar-" + индекс; } Пока (Directory.Exists(путь)); Directory.CreateDirectory(путь); DoStuffWith(путь); Directory.Delete(путь, правда); /**/ Теперь, пока этот процесс работает, работает и другой процесс. Мой процесс передает «/tmp/foobar-0» в Directory.Exists и возвращает false. Другой процесс создает «/tmp/foobar-0», думая, что он принадлежит ему. Мой процесс передает «/tmp/foobar-0» в Directory.CreateDirectory. Нет ошибки. Мой процесс использует каталог, а затем удаляет поддерево. У другого процесса просто выдернули ковер из-под него.

Jonathan Gilbert 02.07.2024 22:41

Идиоматический подход на уровне системного API — попытаться создать каталог и следить за ERROR_ALREADY_EXISTS (Windows) или EEXIST (Linux). Затем, если туда первым доберется другой процесс, я получаю сообщение об ошибке и могу попробовать другое имя. Если я приду туда первым, то другой процесс получит ошибку. В любом случае оба процесса могут знать, «затребовал» ли уже другой процесс этот путь. Но .NET не раскрывает это и молча скрывает все ошибки в Directory.CreateDirectory.

Jonathan Gilbert 02.07.2024 22:43

Судя по вашему описанию, я ДУМАЮ, что ваша настоящая проблема заключается в том, как безопасно удалить созданный вами каталог, когда несколько копий вашего кода выполняются одновременно. Один из общих подходов — создать независимую службу, которой все ваши параллельные экземпляры программы отправляют свои запросы на создание/удаление, чтобы она могла определить, когда она больше не нужна никому из них. Предполагая, что вам не нужно беспокоиться о программах, кроме ваших собственных, использующих каталог, ваша служба будет единственной, которая сможет создавать/удалять, поэтому вам не придется беспокоиться о гонках. Для задач/потоков существуют более простые методы блокировки.

Rich K 02.07.2024 22:52

В качестве альтернативы, и это намного проще, если вашим параллельным процессам на самом деле не нужен доступ к тому же каталогу, вы можете просто использовать Directory.CreateTempSubdirectory(), который создаст подкаталог с уникальным/случайным подкаталогом для каждого процесса. Тогда каждый процесс предположительно сможет удалить свой уникальный подкаталог, когда захочет.

Rich K 02.07.2024 23:13

Это проблема XY (как подозревает @RichK)? Можете ли вы раскрыть, что стоит за вашим вопросом? Какой процесс мешает вам создать свой прекрасный каталог?

rotabor 02.07.2024 23:16

Мм, нет, на самом деле я пишу автоматический тест, и ему нужен временный каталог на время теста, и мне нужен самый прямой способ, чтобы он был абсолютно уверен, что он удаляет только свои собственные файлы. Я мог бы просто дать каталогу имя на основе GUID или текущего времени в тиках, но это хаки, когда API предоставляет правильный механизм для обнаружения и разрешения коллизий.

Jonathan Gilbert 02.07.2024 23:47
Directory.CreateTempSubdirectory() это решение.
rotabor 02.07.2024 23:50

Круто :-) Я посмотрел его реализацию. В Windows он делает в основном то же, что и я: вызывает CreateDirectory и выполняет цикл, если он когда-либо возвращает ERROR_ALREADY_EXISTS. Интересно, что POSIX на самом деле содержит базовую функцию, которая делает именно это за вас, под названием mkdtemp, и реализация CreateTempSubdirectory в UNIX использует ее. Вы даете ему шаблон, он создает вам каталог и возвращает имя. Хороший. :-) Это ответ на этот вопрос.

Jonathan Gilbert 02.07.2024 23:57

Пожалуйста, добавляйте свой код в конец сообщения, а не в комментарии.

user246821 03.07.2024 01:55

Должно быть очевидно, что ваш дизайн ошибочен. Кроме того, если вы не из будущего, компьютерные программы не думают. Компьютерные программы выполняют инструкции, которые вы запрограммировали. Если есть состояние гонки или тупик, значит, ваш проект потерпел неудачу.

user246821 03.07.2024 01:57

Ух ты. Их боевые слова. Кроме того, это очень ошибочные боевые слова. Возможно, вы захотите отозвать их, потому что другие люди, читающие это взад и вперед, будут очень сбиты с толку тем, что вы только что написали.

Jonathan Gilbert 03.07.2024 02:06

Им ругаются слова: Как так? Конструктивные ошибки случаются. Это не проблема, если только кто-то не отказывается признать проблему и исправить ее. Возможно, вы захотите отозвать их, потому что другие люди, читающие это туда и обратно, будут очень сбиты с толку тем, что вы только что написали: возможно, вы могли бы удалить весь пост. Ему не хватает минимального воспроизводимого примера, и без него он не имеет смысла. Вероятно, этот пост когда-нибудь будет удален модераторами.

user246821 03.07.2024 04:53

Я действительно чувствую, что вы в корне неправильно понимаете практически все, что касается этого поста и связанных с ним обсуждений.

Jonathan Gilbert 03.07.2024 04:54

Вы правы, я мало что понимаю в этом посте. Если вы нажмете на мой идентификатор пользователя, вы заметите, что у меня нет вопросов, только ответы, а это означает, что я читаю много сообщений. К сожалению, в этом посте отсутствует много важной информации.

user246821 03.07.2024 04:58

Это действительно не так. С одной стороны, у вас есть API-интерфейсы ОС, которые повсеместно имеют функцию, создающую каталог, но только в том случае, если он не существует. Все они возвращают код ошибки, если он уже существует. Большинство из них явно документируют, что вы можете зациклиться, если вам нужно убедиться, что именно вы создали каталог. UNIX предоставляет функциональные возможности, которые сделают это за вас с помощью mkdtemp. Даже .NET Directory.CreateTempSubdirectory делает именно это за вас. Это соглашение. Но реализовать/следовать с помощью Directory.CreateDirectory невозможно.

Jonathan Gilbert 03.07.2024 17:03
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
21
106
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Создайте каталог где-нибудь еще на диске, а затем используйте метод Directory.Move. Это не удается, если целевой каталог существует.

Это гарантирует, что вы являетесь творцом, если Move не подвел.

P. S. Directory.CreateTempSubdirectory создает временный каталог во временном каталоге текущего пользователя, и это не универсальное решение с точки зрения расположения каталога, но в большинстве случаев лучшее.

С другой стороны, приведенное выше решение с использованием достаточно длинного случайного составленного имени решает задачу на практике. Также очень надуманным выглядит предлог о том, что процесс пытается создать каталог везде (чтобы пользовательское приложение не создавало свой собственный каталог).

С технической точки зрения, здесь все еще существует состояние гонки, потому что этот каталог «где-то еще на диске» не обязательно будет вашим. Думаю, вы можете дать ему GUID для имени или чего-то еще :-P Классный трюк, спасибо.

Jonathan Gilbert 02.07.2024 22:38

«Где-то еще на диске» не обязательно будет вашим, но гарантирует, что оно будет вашим в целевом месте, как вы и просили.

rotabor 02.07.2024 22:42

Да, конечно, но предположим, что код по наивности всегда создает /tmp/NewDirectory, а затем перемещает его на место. Теперь, если есть другой процесс, использующий тот же путь для своих целей, мой процесс украдет его каталог и переместит его куда-нибудь еще. Есть много способов сделать это маловероятным, но это пробел с точки зрения доказуемой правильности.

Jonathan Gilbert 02.07.2024 22:46

Вам нужен практический результат или провести теоретическое исследование?

rotabor 02.07.2024 22:49

Я хочу написать код, который с самого начала будет доказуемо правильным, чтобы не было пробелов. В этот момент мое приложение P/вызывает CreateDirectory (или mkdir в Linux), чтобы я мог проверить код ошибки и заранее обнаружить коллизии.

Jonathan Gilbert 02.07.2024 23:45

@JonathanGilbert: Верно, конечно, вам нужно, чтобы временное имя включало что-то случайное или уникальное для этого потока (например, идентификатор потока), а не то же имя, которое будут использовать другие потоки того же процесса или другие экземпляры программы. Unix rename является атомарным только внутри одной файловой системы, поэтому обычно вам нужно создать его в том же каталоге, что и пункт назначения перемещения. Если это /tmp, то /tmp/xyz$PID может сработать

Peter Cordes 03.07.2024 03:09

Это соглашение, которое, если все ему следуют, гарантирует уникальность. Но если это только ваше соглашение, кто-то другой может придумать совершенно независимое xyz### соглашение и создать коллизию. Это маловероятно, но это может произойти, и вы можете спроектировать вещи так, чтобы этого не произошло.

Jonathan Gilbert 03.07.2024 04:53

@JonathanGilbert: Не видел вашего ответа раньше, так как вы @ не уведомили меня. Да, это возможно, особенно если вредоносный процесс пытается создать конфликты с предсказуемым форматом/шаблоном; вот почему системный вызов POSIX mkdir возвращает ошибку, если каталог уже существует. Если вы делаете это в подкаталоге, где что-то должен делать только ваш процесс, а ненадежные пользователи не могут его писать, это имеет большое значение. Но если у вас этого нет, то CreateDirectory, который не различает уже существующее или нет, недостаточно. Этот ответ не охватывает этого.

Peter Cordes 03.07.2024 08:40

@PeterCordes Конечно. Дело в том, что и в Windows, и в Linux существует соглашение, согласно которому API, создающий каталог, будет возвращать ошибку, если он уже существует. Исходя из этого, в машинном коде существует естественное соглашение: «Создайте путь, попытайтесь создать его, повторите при необходимости», чтобы получить принадлежащий вам каталог. Все приложения будут делать это, кроме приложений, написанных на .NET, потому что Directory.CreateDirectory так не работает. Хотя даже Directory.CreateTempSubdirectory так делает.

Jonathan Gilbert 03.07.2024 17:01

Кстати, Directory.CreateTempSubdirectory можно использовать для создания каталога, созданного в первую очередь, создателем которого вы знаете, что вы были, а затем этот каталог можно переместить на место с помощью Directory.Move, что потерпит неудачу, если кто-то другой окажется там непосредственно перед вами. Состояние гонки между Directory.Exists и созданием закрыто. Таким образом, это обеспечивает общее решение проблемы. :-)

Jonathan Gilbert 03.07.2024 17:05

@rotabor См. мой предыдущий комментарий, где есть соответствующая мысль, которая только что пришла мне в голову.

Jonathan Gilbert 03.07.2024 17:06
Ответ принят как подходящий

Ответ @rotabor о создании каталога в другом месте, а затем использовании Directory.Move, чтобы попытаться разместить его на месте, имеет пробел в том, что «создание каталога в другом месте» имеет точно такую ​​же проблему состояния гонки, но .NET предоставляет функцию Directory.CreateTempSubdirectory, которая может это исправить. зазор. Сначала используйте Directory.CreateTempSubdirectory, чтобы создать каталог, автором которого вы знаете, что вы были, затем используйте Directory.Move, чтобы переместить его по целевому пути. Это может потерпеть неудачу, даже если вы заранее проверили Directory.Move, из-за возможности того, что какой-то другой процесс прокрадется впереди вас, и тогда вы можете попробовать другой путь и цикл или обработать его иным образом по мере необходимости.

Кроме того, если вам не нужен конкретный целевой путь, а нужен только какой-то путь, которым вы владеете, вы можете просто использовать Directory.CreateTempSubdirectory и покончить с этим.

Другие вопросы по теме

Foreach нельзя использовать для переменных типа «PropertyInfo», поскольку «PropertyInfo» не имеет определения для «GetEnumerator»
Положение большого пальца динамической сетки данных WPF изменилось, когда определение строки и столбца добавляется в выбранную динамическую сетку
Указание симулятора IOS при запуске приложения с помощью инструментов командной строки .NET
Код ошибки MSB4062 в проекте функции Azure в VS для запуска API Microsoft Graph с функциями Azure
Отключить закрытие начальной загрузки вне холста при щелчке по фону в приложении Blazor .NET
Целые числа, допускающие значение NULL – почему не возникает исключение?
Можем ли мы предоставить участнику данных BLOB-объектов хранилища разрешение на регистрацию приложения Azure?
Как запустить EF Core ExecuteUpdateAsync и добавить новый объект как часть этого обновления?
С этим соединением уже связан открытый DataReader, который необходимо закрыть при первой ошибке
Динамический сборщик .net MAUI не показывает данные

Похожие вопросы

Почему INumber<T>.CreateX(int n) настолько медленный по сравнению с неявным преобразованием для чисел с плавающей запятой и двойной точности?
VS 2022 Добавить ссылку на службу, не генерирующую ClientCredentials для .Net Framework
Как определить конкретный шаблон для конфигурации пути пряжи
Foreach нельзя использовать для переменных типа «PropertyInfo», поскольку «PropertyInfo» не имеет определения для «GetEnumerator»
Некоторые свойства отсутствуют в DirectX 11 по сравнению с 9-й версией
Как отделить SQL от кода при использовании параметризованных запросов?
Использование Effort с расширениями EF. WhereBulkContains выдает System.Exception
Как предотвратить вызов загруженных выгруженных методов Pages и UC, когда пользователь повторно подключает RDC в WPF C#?
Удалить во вложенных циклах (удалить внутри foreach)
Положение большого пальца динамической сетки данных WPF изменилось, когда определение строки и столбца добавляется в выбранную динамическую сетку