Как я должен обработать исключения в моем Располагать () метод?

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

static void DoSomethingThatNeedsATemporaryDirectory()
{
    using (var tempDir = new TemporaryDirectory())
    {
        // Use the directory here...
        File.WriteAllText(Path.Combine(tempDir.Path, "example.txt"), "foo\nbar\nbaz\n");
        // ...
        if (SomeCondition)
        {
            return;
        }
        if (SomethingIsWrong)
        {
            throw new Exception("This is an example of something going wrong.");
        }
    }
    // Regardless of whether we leave the using block via the return,
    // by throwing and exception or just normally dropping out the end,
    // the directory gets deleted by TemporaryDirectory.Dispose.
}

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

Кажется, что у нас есть четыре опции:

  1. Поймайте и глотайте любое исключение при попытке удалить каталог. Нас можно было бы оставить не зная, что нам не удается очистить наш временный каталог.
  2. Так или иначе обнаружьте, если Расположение работает как часть раскручивания стека, когда исключение было выдано, и раз так или подавите IOException или выдайте исключение, которое соединяет IOException и независимо от того, что другое исключение было выдано. Даже не могло бы быть возможным. (Я думал об этом частично, потому что это будет возможно с менеджерами по контексту Python, которые во многих отношениях подобны IDisposable.NET, используемому с оператором использования C#.)
  3. Никогда не подавляйте IOException от отказа удалить каталог. Если исключение было выдано в блоке использования, мы скроем его, несмотря на то, чтобы там быть хорошим шансом, что это имело больше диагностического значения, чем наш IOException.
  4. Разочаруйтесь в удалении каталога в Расположить методе. Пользователи класса должны остаться ответственными за запрос удаления каталога. Это кажется неудовлетворительным, поскольку значительная часть мотивации для создания класса должна была уменьшить нагрузку управления этим ресурсом. Возможно, существует другой способ обеспечить эту функциональность, не делая очень легким завинтить?

Одна из этих опций является ясно лучшей? Существует ли лучший способ обеспечить эту функциональность в удобном для пользователя API?

15
задан P Daddy 24 February 2010 в 12:32
поделиться

7 ответов

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

Directory dir = Directory.CreateDirectory(path);
try
{
    string fileName = Path.Combine(path, "data.txt");
    File.WriteAllText(fileName, myData);
    UploadFile(fileName);
    File.Delete(fileName);
}
finally
{
    Directory.Delete(dir);
}

Как это должно вести себя? Это точно такой же вопрос. Оставляете ли вы содержимое блока finally как есть, тем самым потенциально маскируя исключение, возникающее в блоке try , или заключаете ли вы в оболочку Directory.Delete ] в собственном блоке try-catch , проглатывая любое исключение, чтобы предотвратить маскировку оригинала?

Я не думаю, что есть правильный ответ - факт в том, что вы может иметь только одно внешнее исключение, поэтому вам нужно выбрать одно. Однако .NET Framework создает некоторые прецеденты; одним из примеров являются прокси-серверы службы WCF ( ICommunicationObject ). Если вы попытаетесь удалить канал, в котором произошел сбой, это вызовет исключение, а замаскирует любое исключение, которое уже находится в стеке. Если я не ошибаюсь, TransactionScope тоже может это делать.

Конечно, именно такое поведение WCF было нескончаемым источником путаницы; большинство людей считают его очень раздражающим, если не сломанным. Погуглите "WCF dispose mask", и вы поймете, что я имею в виду. Так что, возможно, нам не всегда следует пытаться делать что-то так же, как это делает Microsoft.

Лично я считаю, что Dispose никогда не должен маскировать исключение, уже находящееся в стеке. Оператор using фактически является блоком finally и большую часть времени (всегда есть крайние случаи), вы не хотели бы бросать (и не ловить) исключения в блоке finally . Причина просто в отладке; может быть чрезвычайно трудно разобраться в сути проблемы - особенно проблема в производственной среде, когда вы не можете пройти через источник - когда у вас даже нет возможности узнать, где именно приложение не работает. Я уже бывал в таком положении раньше и могу с уверенностью сказать, что это полностью сведет вас с ума.

Я бы порекомендовал либо исключить исключение в Dispose (конечно, зарегистрировать его), либо проверить, не находитесь ли вы уже в сценарии раскрутки стека из-за исключение и использовать последующие исключения только в том случае, если вы знаете, что вы их замаскируете. Преимущество последнего состоит в том, что вы не едите исключения, если вам действительно не нужно; недостаток в том, что вы ввели в свою программу недетерминированное поведение. Еще один компромисс.

Большинство людей, вероятно, просто выберут первый вариант и просто скроют любое исключение, возникающее в , наконец, (или , используя ).

7
ответ дан 1 December 2019 в 05:07
поделиться

В конечном итоге, я бы предположил, что лучше всего следовать FileStream в качестве руководства, что соответствует вариантам 3 и 4: закрывать файлы или удалять каталоги в методе Dispose, и позволять любым исключениям, возникающим как часть этого действия, всплывать (эффективно поглощая любые исключения, возникающие внутри блока using), но позволять закрывать ресурс вручную без блока using, если пользователь компонента так решит.

В отличие от документации MSDN по FileStream, я предлагаю вам тщательно документировать последствия, которые могут произойти, если пользователь решит использовать using.

2
ответ дан 1 December 2019 в 05:07
поделиться

Здесь следует задать вопрос, может ли вызывающая сторона с пользой обработать исключение. Если пользователь не может ничего разумно сделать (вручную удалить используемый файл из каталога?), возможно, лучше записать ошибку в журнал и забыть о ней.

Чтобы охватить оба случая, почему бы не иметь два конструктора (или аргумент к конструктору)?

public TemporaryDirectory()
: this( false )
{
}

public TemporaryDirectory( bool throwExceptionOnError )
{
}

Тогда вы можете переложить решение о том, каким должно быть поведение класса, на пользователя.

Одной из распространенных ошибок будет каталог, который не может быть удален, потому что файл внутри него все еще используется: вы можете хранить список не удаленных временных каталогов и разрешить возможность второй явной попытки удаления во время завершения работы программы (например, статический метод TemporaryDirectory.TidyUp()). Если список проблемных каталогов не является пустым, код может принудительно собирать мусор для обработки незакрытых потоков.

0
ответ дан 1 December 2019 в 05:07
поделиться

Вы не можете полагаться на предположение, что вы можете каким-то образом удалить свой каталог. Какой-то другой процесс / пользователь / кто угодно может тем временем создать в нем файл. Антивирус может быть занят проверкой файлов в нем и т. Д.

Лучшее, что вы можете сделать, - это иметь не только класс временного каталога, но и класс временного файла (который должен быть создан внутри с использованием блока ваш временный каталог. Классы временных файлов должны (попытаться) удалить соответствующие файлы на Dispose . Таким образом вы гарантируете, что, по крайней мере, была сделана попытка очистки.

{{1} }
0
ответ дан 1 December 2019 в 05:07
поделиться

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

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

Если вы ожидаете, что файлы заблокированы, и есть что-то, что вы или, возможно, вызывающий абонент, можете с ними сделать, тогда вам необходимо включить этот ответ в свой класс. Если можете ответить, то просто сделайте это в одноразовом вызове. Если ваш вызывающий абонент может ответить, предоставьте ему способ, например, событие TempfilesLocked.

0
ответ дан 1 December 2019 в 05:07
поделиться

Предполагая, что созданный каталог находится во временной папке системы, подобной той, которую возвращает Path.GetTempPath , я бы реализовал Удалите , чтобы не генерировать исключение в случае сбоя удаления временного каталога.

Обновление: Я бы выбрал этот вариант, потому что операция может завершиться неудачно из-за внешнего вмешательства, например, блокировки от другого процесса, а также из-за того, что каталог временно помещен в систему каталог, я бы не увидел преимущества в генерации исключения.

Что было бы правильным ответом на это исключение? Пытаться удалить каталог снова неразумно, и если причина в блокировке другого процесса, это то, что он не находится под вашим контролем.

0
ответ дан 1 December 2019 в 05:07
поделиться

Чтобы использовать тип в с использованием оператора , вы хотите реализовать шаблон IDisposable .

Для создания самого каталога используйте Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData) в качестве основы и новый Guid в качестве имени.

-1
ответ дан 1 December 2019 в 05:07
поделиться
Другие вопросы по тегам:

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