Есть ли способ убедиться, что именно я создал каталог на C#?
По умолчанию Directory.CreateDirectory по сути является операцией UPSERT: если каталог не существует, он создается, но если он уже существует, то это нормально. Пока он существует по возвращении, все счастливы.
Но я намерен удалить поддерево, когда закончу с ним, и не хочу этого делать, если я не создал его изначально. Насколько я могу судить, единственным способом добиться этого будет P/Invoke, потому что с управляемым API я должен проверять существование отдельно, и между Directory.Exists и Directory.CreateDirectory всегда будет состояние гонки.
Однако в API базовой платформы вызов создания каталога вернет ошибку, если он уже существует (ERROR_ALREADY_EXISTS из CreateDirectoryW в Windows, EEXIST из mkdir в системах POSIX).
Есть ли управляемый способ создания каталога, но только если он не существует в .NET?
Для окон посмотрите github.com/dotnet/runtime/blob/… и используйте только те части, которые вам нужны, и проверьте, не являются ли другие вещи, которые вы там видите, неуместными;)
О, я могу без проблем выполнить P/Invoke, просто предпочитаю этого не делать, если в этом нет необходимости. Но, похоже, в этом случае мне, возможно, придется. Вздох. :-П
Я связал источник, потому что это может быть нечто большее, чем просто вызов Win API. Например, если вы будете следовать «Interop.Kernel32.CreateDirectory», вы увидите не только вызов pinvoke.
Спасибо :-) Я много работал над разработкой Win32 на C/C++, а также на C#, так что вызвать эту функцию не составляет большого труда. Я просто не хочу создавать эту зависимость, если есть управляемый способ сделать это. Хотя в данном случае я его не видел. :-П
с управляемым API я должен проверять существование отдельно, и между _Directory.Exists и Directory.CreateDirectory всегда будет состояние гонки? К сожалению, этот пост не имеет особого смысла. Почему вы считаете, что существует состояние гонки?
Мой процесс: 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. Нет ошибки. Мой процесс использует каталог, а затем удаляет поддерево. У другого процесса просто выдернули ковер из-под него.
Идиоматический подход на уровне системного API — попытаться создать каталог и следить за ERROR_ALREADY_EXISTS (Windows) или EEXIST (Linux). Затем, если туда первым доберется другой процесс, я получаю сообщение об ошибке и могу попробовать другое имя. Если я приду туда первым, то другой процесс получит ошибку. В любом случае оба процесса могут знать, «затребовал» ли уже другой процесс этот путь. Но .NET не раскрывает это и молча скрывает все ошибки в Directory.CreateDirectory.
Судя по вашему описанию, я ДУМАЮ, что ваша настоящая проблема заключается в том, как безопасно удалить созданный вами каталог, когда несколько копий вашего кода выполняются одновременно. Один из общих подходов — создать независимую службу, которой все ваши параллельные экземпляры программы отправляют свои запросы на создание/удаление, чтобы она могла определить, когда она больше не нужна никому из них. Предполагая, что вам не нужно беспокоиться о программах, кроме ваших собственных, использующих каталог, ваша служба будет единственной, которая сможет создавать/удалять, поэтому вам не придется беспокоиться о гонках. Для задач/потоков существуют более простые методы блокировки.
В качестве альтернативы, и это намного проще, если вашим параллельным процессам на самом деле не нужен доступ к тому же каталогу, вы можете просто использовать Directory.CreateTempSubdirectory(), который создаст подкаталог с уникальным/случайным подкаталогом для каждого процесса. Тогда каждый процесс предположительно сможет удалить свой уникальный подкаталог, когда захочет.
Это проблема XY (как подозревает @RichK)? Можете ли вы раскрыть, что стоит за вашим вопросом? Какой процесс мешает вам создать свой прекрасный каталог?
Мм, нет, на самом деле я пишу автоматический тест, и ему нужен временный каталог на время теста, и мне нужен самый прямой способ, чтобы он был абсолютно уверен, что он удаляет только свои собственные файлы. Я мог бы просто дать каталогу имя на основе GUID или текущего времени в тиках, но это хаки, когда API предоставляет правильный механизм для обнаружения и разрешения коллизий.
Directory.CreateTempSubdirectory() это решение.
Круто :-) Я посмотрел его реализацию. В Windows он делает в основном то же, что и я: вызывает CreateDirectory и выполняет цикл, если он когда-либо возвращает ERROR_ALREADY_EXISTS. Интересно, что POSIX на самом деле содержит базовую функцию, которая делает именно это за вас, под названием mkdtemp, и реализация CreateTempSubdirectory в UNIX использует ее. Вы даете ему шаблон, он создает вам каталог и возвращает имя. Хороший. :-) Это ответ на этот вопрос.
Пожалуйста, добавляйте свой код в конец сообщения, а не в комментарии.
Должно быть очевидно, что ваш дизайн ошибочен. Кроме того, если вы не из будущего, компьютерные программы не думают. Компьютерные программы выполняют инструкции, которые вы запрограммировали. Если есть состояние гонки или тупик, значит, ваш проект потерпел неудачу.
Ух ты. Их боевые слова. Кроме того, это очень ошибочные боевые слова. Возможно, вы захотите отозвать их, потому что другие люди, читающие это взад и вперед, будут очень сбиты с толку тем, что вы только что написали.
Им ругаются слова: Как так? Конструктивные ошибки случаются. Это не проблема, если только кто-то не отказывается признать проблему и исправить ее. Возможно, вы захотите отозвать их, потому что другие люди, читающие это туда и обратно, будут очень сбиты с толку тем, что вы только что написали: возможно, вы могли бы удалить весь пост. Ему не хватает минимального воспроизводимого примера, и без него он не имеет смысла. Вероятно, этот пост когда-нибудь будет удален модераторами.
Я действительно чувствую, что вы в корне неправильно понимаете практически все, что касается этого поста и связанных с ним обсуждений.
Вы правы, я мало что понимаю в этом посте. Если вы нажмете на мой идентификатор пользователя, вы заметите, что у меня нет вопросов, только ответы, а это означает, что я читаю много сообщений. К сожалению, в этом посте отсутствует много важной информации.
Это действительно не так. С одной стороны, у вас есть API-интерфейсы ОС, которые повсеместно имеют функцию, создающую каталог, но только в том случае, если он не существует. Все они возвращают код ошибки, если он уже существует. Большинство из них явно документируют, что вы можете зациклиться, если вам нужно убедиться, что именно вы создали каталог. UNIX предоставляет функциональные возможности, которые сделают это за вас с помощью mkdtemp. Даже .NET Directory.CreateTempSubdirectory делает именно это за вас. Это соглашение. Но реализовать/следовать с помощью Directory.CreateDirectory невозможно.





Создайте каталог где-нибудь еще на диске, а затем используйте метод Directory.Move. Это не удается, если целевой каталог существует.
Это гарантирует, что вы являетесь творцом, если Move не подвел.
P. S. Directory.CreateTempSubdirectory создает временный каталог во временном каталоге текущего пользователя, и это не универсальное решение с точки зрения расположения каталога, но в большинстве случаев лучшее.
С другой стороны, приведенное выше решение с использованием достаточно длинного случайного составленного имени решает задачу на практике. Также очень надуманным выглядит предлог о том, что процесс пытается создать каталог везде (чтобы пользовательское приложение не создавало свой собственный каталог).
С технической точки зрения, здесь все еще существует состояние гонки, потому что этот каталог «где-то еще на диске» не обязательно будет вашим. Думаю, вы можете дать ему GUID для имени или чего-то еще :-P Классный трюк, спасибо.
«Где-то еще на диске» не обязательно будет вашим, но гарантирует, что оно будет вашим в целевом месте, как вы и просили.
Да, конечно, но предположим, что код по наивности всегда создает /tmp/NewDirectory, а затем перемещает его на место. Теперь, если есть другой процесс, использующий тот же путь для своих целей, мой процесс украдет его каталог и переместит его куда-нибудь еще. Есть много способов сделать это маловероятным, но это пробел с точки зрения доказуемой правильности.
Вам нужен практический результат или провести теоретическое исследование?
Я хочу написать код, который с самого начала будет доказуемо правильным, чтобы не было пробелов. В этот момент мое приложение P/вызывает CreateDirectory (или mkdir в Linux), чтобы я мог проверить код ошибки и заранее обнаружить коллизии.
@JonathanGilbert: Верно, конечно, вам нужно, чтобы временное имя включало что-то случайное или уникальное для этого потока (например, идентификатор потока), а не то же имя, которое будут использовать другие потоки того же процесса или другие экземпляры программы. Unix rename является атомарным только внутри одной файловой системы, поэтому обычно вам нужно создать его в том же каталоге, что и пункт назначения перемещения. Если это /tmp, то /tmp/xyz$PID может сработать
Это соглашение, которое, если все ему следуют, гарантирует уникальность. Но если это только ваше соглашение, кто-то другой может придумать совершенно независимое xyz### соглашение и создать коллизию. Это маловероятно, но это может произойти, и вы можете спроектировать вещи так, чтобы этого не произошло.
@JonathanGilbert: Не видел вашего ответа раньше, так как вы @ не уведомили меня. Да, это возможно, особенно если вредоносный процесс пытается создать конфликты с предсказуемым форматом/шаблоном; вот почему системный вызов POSIX mkdir возвращает ошибку, если каталог уже существует. Если вы делаете это в подкаталоге, где что-то должен делать только ваш процесс, а ненадежные пользователи не могут его писать, это имеет большое значение. Но если у вас этого нет, то CreateDirectory, который не различает уже существующее или нет, недостаточно. Этот ответ не охватывает этого.
@PeterCordes Конечно. Дело в том, что и в Windows, и в Linux существует соглашение, согласно которому API, создающий каталог, будет возвращать ошибку, если он уже существует. Исходя из этого, в машинном коде существует естественное соглашение: «Создайте путь, попытайтесь создать его, повторите при необходимости», чтобы получить принадлежащий вам каталог. Все приложения будут делать это, кроме приложений, написанных на .NET, потому что Directory.CreateDirectory так не работает. Хотя даже Directory.CreateTempSubdirectory так делает.
Кстати, Directory.CreateTempSubdirectory можно использовать для создания каталога, созданного в первую очередь, создателем которого вы знаете, что вы были, а затем этот каталог можно переместить на место с помощью Directory.Move, что потерпит неудачу, если кто-то другой окажется там непосредственно перед вами. Состояние гонки между Directory.Exists и созданием закрыто. Таким образом, это обеспечивает общее решение проблемы. :-)
@rotabor См. мой предыдущий комментарий, где есть соответствующая мысль, которая только что пришла мне в голову.
Ответ @rotabor о создании каталога в другом месте, а затем использовании Directory.Move, чтобы попытаться разместить его на месте, имеет пробел в том, что «создание каталога в другом месте» имеет точно такую же проблему состояния гонки, но .NET предоставляет функцию Directory.CreateTempSubdirectory, которая может это исправить. зазор. Сначала используйте Directory.CreateTempSubdirectory, чтобы создать каталог, автором которого вы знаете, что вы были, затем используйте Directory.Move, чтобы переместить его по целевому пути. Это может потерпеть неудачу, даже если вы заранее проверили Directory.Move, из-за возможности того, что какой-то другой процесс прокрадется впереди вас, и тогда вы можете попробовать другой путь и цикл или обработать его иным образом по мере необходимости.
Кроме того, если вам не нужен конкретный целевой путь, а нужен только какой-то путь, которым вы владеете, вы можете просто использовать Directory.CreateTempSubdirectory и покончить с этим.
Эти методы пытаются создать весь путь, вам это нужно? Вы упоминаете POSIX, поэтому пытаетесь или должны оставаться независимыми от системы?