System.IO.IOException: “Файл існує” при використанні System.IO.Path.GetTempFileName () - резолюції?


84

Один із моїх клієнтів отримував виняток, коли він намагався використовувати мій продукт. Я отримав код виклику виключення, яке сталося, вершина якого:

at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.__Error.WinIOError()
   at System.IO.Path.GetTempFileName()
   at System.Windows.Input.Cursor.LoadFromStream(Stream cursorStream)
   at System.Windows.Input.Cursor..ctor(Stream cursorStream)

Погугливши це, я виявив безліч публікацій у блозі, в яких зазначається, що цей виняток видається, коли в папці% TEMP% є більше 65535 тимчасових файлів, і що рішення полягає в тому, щоб просто очистити старі тимчасові файли. Я можу попросити замовника зробити це, але це може бути лише тимчасовим рішенням - а що, якщо вони регулярно запускають якусь іншу частину програмного забезпечення, яка часто робить дзвінки до GetTempFileName, що змушує проблему повторюватися знову і знову?

Я не можу просто програмно очистити папку% TEMP%, оскільки це може якимось чином пошкодити щось інше, і я не можу уникнути виклику GetTempFileName (і використання натомість власної тимчасової папки), оскільки це викликаю не я, а код WPF.

Чи існує якесь постійне рішення для цього?

ОНОВЛЕННЯ : Я підтвердив, що проблема, коли папка% TEMP% переповнена файлами журналів, не викликана моїм власним кодом, а повинна бути спричинена якоюсь іншою стороннім додатком на машині клієнта. Я також розглянув реалізацію, Cursor.LoadFromStreamі це, безсумнівно, не винна - вона генерує тимчасовий файл, але потім видаляє його в finallyблоці.


3
Ви можете створити свою власну папку "Temp", яка видаляється (у даних програми "), але, мабуть, це буде балаш для зміни всіх посилань, гарне запитання
Сайс

Питання не пов’язане з WPF, видалений тег. Крім того, чому просто не виправити код, який створює стільки тимчасових файлів без видалення?
Денніс

1
@Sayse Я не можу цього зробити, оскільки саме WPF Cursor.LoadFromStreamгенерує тимчасовий файл. @Dennis Це пов'язано з Cursor.LoadFromStreamкласом WPF . Код-порушник, який створює стільки тимчасових файлів без видалення, може бути навіть не моїм, і мені все одно доведеться вирішити виняток.
Омер Равів,

Чи можете ви дізнатися, яка програма залишає позаду всі ці тимчасові файли? Це ваша заявка? Якщо WPF сам створює ці тимчасові файли, ви підтвердили, що видаляє їх, коли вони більше не потрібні?
Ashigore

2
@OmerRaviv Я думаю, що тоді ти єдиний варіант - спробувати / зловити IOE і запитати у користувача, чи не хоче він видалити тимчасові файли, і повторити спробу
Сайс

Відповіді:


15

Як я вже згадував у своєму останньому коментарі, я вважаю, що ваш єдиний безпечний спосіб зробити це - запитати у користувача, чи хоче він видалити файли, і повторити спробу. Вкрай важливо, щоб ви залучили користувачів до цього, таким чином, це на їх власний ризик. У моїй голові це щось подібне.

public Stream GetStream(Stream cursorStream)
{
    try
    {
       //getting stream
    }
    catch(IOE)
    {
        MessageBox.Show(this, "Unable to get stream, your temporary
                              folder may be full, do you want to try deleting 
                                some and try again?");
         if(yes)
         try
         {
             //delete and try again
             return GetStream(cursorStream);
         }
         catch(IOE)
          {
                //no luck
           }
          else
              return null;
    }

}

Необов’язкова перевірка, щоб переконатися, що це може бути,

Directory.EnumerateFiles(Path.GetTempPath(), "*", SearchOption.TopLevelOnly)
  .Count() == ushort.MaxValue;

2
Я піду спробувати / зловити, як ти запропонував. Булевий тест, який ви запропонували, насправді помилковий - швидше за все, в папці% TEMP% є інші файли, крім файлів у форматі "tmpXXXX.tmp", який використовує GetTempFileName (), тому ваш тест може повернути істину, коли насправді немає проблем і false, коли є проблема.
Омер Равів,

Тест був розроблений, щоб знайти всі файли у тимчасовій папці (хоча я і визнаю, що це може бути не правильна тимчасова папка) і перевірить, чи дорівнює це 65535, "*"знайде всі файли (незалежно від того, мають вони розширення чи ні), сподіваюся, це допоможе
Сайс

Ви можете видалити всі файли, старші, скажімо, за один день. Тимчасовий файл, старший за один день, навряд чи буде використовуватися будь-якою іншою програмою.
JT Taylor

2
Просто ще один пункт додаткової перевірки. Коли я зіткнувся з цією проблемою на сервері збірки, у тимчасовій директорії було понад 65535 файлів. Цей підрахунок працює лише в тому випадку, якщо ЄДИНА річ, яка створює тимчасові файли, здійснюється через допоміжний клас Path.
rshadman

36

Якщо це трапляється з вами у виробничому середовищі або з додатком, який ви не можете змінити, швидке вирішення проблеми - спорожнення папки Temp.

Залежно від користувача, який запускає програму, вам слід зробити це

  • Порожній C:\Windows\Temp(для IIS або служб, що працюють під LocalSystemобліковим записом)
  • Або %temp%для локально зареєстрованих користувачів (що для мене є C:\Users\MyUserName\AppData\Local\Temp).

З іншого боку, якщо ваш власний код кидає це, і ви хочете, щоб це не повторювалося ніколи:

  1. Не використовуйте System.IO.Path.GetTempFileName ()!

GetTempFileName()є обгорткою двох десятиліть Win32 Api . Він генерує імена файлів, які дуже легко зіткнуться. Він обходить ці зібрання, сильно зациклюючись на файловій системі, перебираючи можливі імена файлів від "%temp%\tmp0000.tmp"до "tmpFFFF.tmp"та пропускаючи вже існуючі. Це інтенсивний, повільний і відверто жахливий алгоритм вводу-виводу. Також використання лише 4 шістнадцяткових символів - це те, що робить штучне обмеження 65536 файлів перед збоєм.

Альтернативою є створення імен файлів, які не зіткнуться. Наприклад, дозволяє повторно використовувати GUID'sлогіку: 32 шістнадцяткові цифри майже ніколи не зіткнуться.

private string GetTempFileName()
{
    return Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
}
// Sample: c:\Windows\Temp\2e38fe87-f6bb-4b0d-90b3-2d07016324c1

Це розширює обмеження з 65 тис. До 4 тис. Мільйонів файлів (теоретично) ... Звичайно, витік 65 тис. Файлів - це вже страшно, тому ...

  1. Не витікайте тимчасові файли!

Перевірте свою програму на всі щасливі та нещасні шляхи (як несподівані винятки). Переконайтеся, що він правильно розпоряджається кожним FileStream та видаляє тимчасові файли в блоках нарешті.

  1. Очистіть папку temp

Очистіть його зараз і навчайте системного адміністратора періодично чистити його, оскільки ви не можете довіряти кожному додатку в дикій природі. На власних серверах я б автоматизував це завдання, використовуючи:

  • Для глобальної Windows \ Temp

schtasks /Create /TR "cmd /c call DEL /F /S /Q %^TEMP%" /TN "Delete Global Temp Files" /sc WEEKLY /ST 12:00 /ru system

  • Для поточного користувача:

schtasks /Create /TR "cmd /c call DEL /F /S /Q %^TEMP%" /TN "Delete %username% Temp Files" /sc WEEKLY /ST 12:00


5

Ось код, який я використав врешті-решт, і помістив його на початку кодового шляху ініціалізації мого додатка до того, як Cursor.LoadFromStreamможуть відбутися будь-які виклики :

    private void WarnUserIfTempFolderFull()
    {
        string tempFile = null;
        try
        {
            tempFile = Path.GetTempFileName();
        }
        catch (IOException e)
        {
            string problem = "The Temporary Folder is full.";

            string message = "{ProductName} has detected that the Windows Temporary Folder is full. \n" + 
                             "This may prevent the {ProductName} from functioning correctly.\n" + 
                             "Please delete old files in your temporary folder (%TEMP%) and try again.";

            Logger.Warn(problem);

            MessageBox.Show(message, caption: problem);
        }
        finally
        {
            if (tempFile != null) File.Delete(tempFile);
        }
    }

2

Рішення:

  1. Правильний. Виявляйте, яка програма створює стільки тимчасових файлів, і не видаляйте їх. Службові програми на зразок Process monitorповинні вам допомогти. Потім або виправте додаток, або викиньте його. І так, це може бути ваша заявка. тому я рекомендую вам виявити джерело зла.
  2. Найпростіший. Використовуйте свій власний тимчасовий каталог. Це не допоможе, якщо файли створюються з вашого коду.
  3. Найпотворніший. Видаліть тимчасовий каталог із вашої програми. Ви абсолютно праві щодо наслідків - ви можете розірвати іншу заявку.

Використання власного тимчасового каталогу - це не обов’язково рішення. Я використовую API для створення імені файлу, але записую його у свій власний каталог. На жаль, навіть ЦЕ не вдається.
Джордж Мауер

2
// one more implementation
string GetTempFileName()
{
    return Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
}

1

Як запропонував Сайсе , ви можете спробувати встановити змінну середовища% TEMP% під час запуску програми.

Environment.SetEnvironmentVariable("TEMP", "<dir>");

Це чудова ідея, але, на жаль, мій додаток - це розширення Visual Studio, яке має мирно існувати разом з іншими розширеннями, і я боюся, що це може якось ненавмисно пошкодити поведінку інших розширень.
Omer Raviv

Це рішення не допоможе, якщо це його власна програма, яка залишає позаду всі ці файли. Новий каталог просто заповниться, і погано самовільно видаляти файли з папки, коли ви навіть не уявляєте, для чого вони призначені, навіть якщо це папка, специфічна для вашої програми.
Ashigore

@Ashigore Так, очевидно, це не виправить помилку, яку він створив. Конкретно посилання what if they are regularly running some other piece of software that makes frequent calls to GetTempFileName.
Ед Чапел,

@OmerRaviv Ця інформація корисна. Насправді це не спрацює в цьому сценарії.
Ед Чапел,

1
Вибачте, @EdChapel, SO заблокував мій голос, я не можу його видалити, якщо відповідь не відредаговано.
Херардо Гриньолі

1

Для всіх, хто стикався з цією проблемою і не може знайти переповнювану папку temp - перевірте папку "C: / Windows / Temp". Очищення цієї папки вирішило мої проблеми.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.