2009-09-10 3 views
22

Как подождать, пока файл будет свободным, так что ss.Save() может перезаписать его новым. Если я запустил это дважды близко (ish), я получаю ошибку generic GDI+.Подождите, пока файл будет освобожден процессом

///<summary> 
    /// Grabs a screen shot of the App and saves it to the C drive in jpg 
    ///</summary> 
    private static String GetDesktopImage(DevExpress.XtraEditors.XtraForm whichForm) 
    { 
     Rectangle bounds = whichForm.Bounds; 

     // This solves my problem but creates a clutter issue 
     //var timeStamp = DateTime.Now.ToString("ddd-MMM-dd-yyyy-hh-mm-ss"); 
     //var fileName = "C:\\HelpMe" + timeStamp + ".jpg"; 

     var fileName = "C:\\HelpMe.jpg"; 
     File.Create(fileName); 
     using (Bitmap ss = new Bitmap(bounds.Width, bounds.Height)) 
     using (Graphics g = Graphics.FromImage(ss)) 
     { 
      g.CopyFromScreen(whichForm.Location, Point.Empty, bounds.Size); 
      ss.Save(fileName, ImageFormat.Jpeg); 
     } 

     return fileName; 
    } 
+3

возможный дубликат из [Есть ли способ проверить, используется ли файл?] (http://stackoverflow.com/questions/876473/is-there-a-way-to-check-if-a-file-is-in -use) –

+0

Этот код имеет простую ошибку с 'File.Create (fileName)'. Ответы отсутствуют. Нет необходимости ждать закрытия. – usr

ответ

45

функция, как это сделать:

public static bool IsFileReady(String sFilename) 
    { 
     // If the file can be opened for exclusive access it means that the file 
     // is no longer locked by another process. 
     try 
     { 
      using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None)) 
      { 
       if (inputStream.Length > 0) 
       { 
        return true; 
       } 
       else 
       { 
        return false; 
       } 

      } 
     } 
     catch (Exception) 
     { 
      return false; 
     } 
    } 

Палка в цикле в то время как и у вас есть то, что будет блокировать, пока файл не доступен

+0

Спасибо! Я бросил это там 'var isReady = false; while (! IsReady) { isReady = IsFileReady (имя_файла); } ' и все кажется хорошо. –

+61

вы также можете сделать 'return inputStream.Length> 0;'. Мне никогда не нравились те, if (condition) return true; else return false; '.. – Default

+6

@Default Я думаю, что возвращение true/false более читаемо –

2

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

3
bool isLocked = true; 
while (isLocked) 
try { 
    System.IO.File.Move(filename, filename2); 
    isLocked = false; 
} 
catch { } 
System.IO.File.Move(filename2, filename); 
2

Вы можете позволить системе ждать , пока процесс не будет закрыт.

Просто так просто, как это:

Process.Start("the path of your text file or exe").WaitForExit();

8

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

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

получения потока

private FileStream GetWriteStream(string path, int timeoutMs) 
{ 
    var time = Stopwatch.StartNew(); 
    while (time.ElapsedMilliseconds < timeoutMs) 
    { 
     try 
     { 
      return new FileStream(path, FileMode.Create, FileAccess.Write); 
     } 
     catch (IOException e) 
     { 
      // access error 
      if (e.HResult != -2147024864) 
       throw; 
     } 
    } 

    throw new TimeoutException($"Failed to get a write handle to {path} within {timeoutMs}ms."); 
} 

затем использовать его как это:

using (var stream = GetWriteStream("path")) 
{ 
    using (var writer = new StreamWriter(stream)) 
     writer.Write("test"); 
} 

повторов Объем

private void WithRetry(Action action, int timeoutMs = 1000) 
{ 
    var time = Stopwatch.StartNew(); 
    while(time.ElapsedMilliseconds < timeoutMs) 
    { 
     try 
     { 
      action(); 
      return; 
     } 
     catch (IOException e) 
     { 
      // access error 
      if (e.HResult != -2147024864) 
       throw; 
     } 
    } 
    throw new Exception("Failed perform action within allotted time."); 
} 

, а затем использовать WithRetry (() => File.WriteAllText (Path.Combine (_directory, имя), содержание));

+0

Я также создал сущность для класса, обертывающего это поведение. Имейте в виду, конечно, что это может означать, что ваша архитектура имеет проблемы, если несколько классов читают и записывают в один и тот же файл конфликтующим образом. Таким образом, вы можете потерять данные. https://gist.github.com/ddikman/667f309706fdf4f68b9fab2827b1bcca – Almund

+0

Я не знаю, почему это не принятый ответ. Код намного безопаснее; вызывая 'IsFileReady' в цикле' while', так как ответ Гордона Томпсона может потенциально потерпеть неудачу. Другой процесс может заблокировать файл между тем, когда условие цикла проверяет, доступно ли его доступное и ваш процесс, к которому он действительно обращается. Единственное, что «e.HResult» недоступно, потому что оно «защищено». –

+0

Спасибо за поддержку, хотя мое предложенное решение довольно захламлено в сравнении. Мне не очень нравится его внешний вид, так как нет встроенной поддержки в рамках, когда у вас осталось несколько вариантов. Я использовал HResult, хотя, может быть, разные версии каркаса, возможно, я уверен, что есть другое свойство, которое может быть использовано для определения того, какая ошибка содержит IOException. – Almund

2

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

Пользователь регистрирует файлы, которые они хотели бы посмотреть, позвонив по номеру FileAccessWatcher.RegisterWaitForFileAccess(filePath). Если файл еще не просматривается, запускается новая задача, которая повторно проверяет файл, чтобы узнать, можно ли его открыть. Каждый раз, когда он проверяет, он также считывает размер файла. Если размер файла не увеличивается в заданное время (5 минут в моем примере), цикл завершается.

Когда цикл выходит из файла, доступного или с момента ожидания, запускается событие FileFinishedCopying.

public class FileAccessWatcher 
{ 
    // this list keeps track of files being watched 
    private static ConcurrentDictionary<string, FileAccessWatcher> watchedFiles = new ConcurrentDictionary<string, FileAccessWatcher>(); 

    public static void RegisterWaitForFileAccess(string filePath) 
    { 
     // if the file is already being watched, don't do anything 
     if (watchedFiles.ContainsKey(filePath)) 
     { 
      return; 
     } 
     // otherwise, start watching it 
     FileAccessWatcher accessWatcher = new FileAccessWatcher(filePath); 
     watchedFiles[filePath] = accessWatcher; 
     accessWatcher.StartWatching(); 
    } 

    /// <summary> 
    /// Event triggered when the file is finished copying or when the file size has not increased in the last 5 minutes. 
    /// </summary> 
    public static event FileSystemEventHandler FileFinishedCopying; 

    private static readonly TimeSpan MaximumIdleTime = TimeSpan.FromMinutes(5); 

    private readonly FileInfo file; 

    private long lastFileSize = 0; 

    private DateTime timeOfLastFileSizeIncrease = DateTime.Now; 

    private FileAccessWatcher(string filePath) 
    { 
     this.file = new FileInfo(filePath); 
    } 

    private Task StartWatching() 
    { 
     return Task.Factory.StartNew(this.RunLoop); 
    } 

    private void RunLoop() 
    { 
     while (this.IsFileLocked()) 
     { 
      long currentFileSize = this.GetFileSize(); 
      if (currentFileSize > this.lastFileSize) 
      { 
       this.lastFileSize = currentFileSize; 
       this.timeOfLastFileSizeIncrease = DateTime.Now; 
      } 

      // if the file size has not increased for a pre-defined time limit, cancel 
      if (DateTime.Now - this.timeOfLastFileSizeIncrease > MaximumIdleTime) 
      { 
       break; 
      } 
     } 

     this.RemoveFromWatchedFiles(); 
     this.RaiseFileFinishedCopyingEvent(); 
    } 

    private void RemoveFromWatchedFiles() 
    { 
     FileAccessWatcher accessWatcher; 
     watchedFiles.TryRemove(this.file.FullName, out accessWatcher); 
    } 

    private void RaiseFileFinishedCopyingEvent() 
    { 
     FileFinishedCopying?.Invoke(this, 
      new FileSystemEventArgs(WatcherChangeTypes.Changed, this.file.FullName, this.file.Name)); 
    } 

    private long GetFileSize() 
    { 
     return this.file.Length; 
    } 

    private bool IsFileLocked() 
    { 
     try 
     { 
      using (this.file.Open(FileMode.Open)) { } 
     } 
     catch (IOException e) 
     { 
      var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1); 

      return errorCode == 32 || errorCode == 33; 
     } 

     return false; 
    } 
} 

Пример использования:

// register the event 
FileAccessWatcher.FileFinishedCopying += FileAccessWatcher_FileFinishedCopying; 

// start monitoring the file (put this inside the OnChanged event handler of the FileSystemWatcher 
FileAccessWatcher.RegisterWaitForFileAccess(fileSystemEventArgs.FullPath); 

Ручка FileFinishedCopyingEvent:

private void FileAccessWatcher_FileFinishedCopying(object sender, FileSystemEventArgs e) 
{ 
    Console.WriteLine("File finished copying: " + e.FullPath); 
} 
0

Вы можете использовать оператор блокировки с фиктивной переменной, и, кажется, отлично работает.

Проверить here.

0

Используя ответ @Gordon Томпсона, вы должны создать цикл, такие как код ниже:

public static bool IsFileReady(string sFilename) 
{ 
    try 
    { 
     using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None)) 
      return inputStream.Length > 0; 
    } 
    catch (Exception) 
    { 
     return false; 
    } 
} 

while (!IsFileReady(yourFileName)) ; 

Я нашел оптимизированный способ, который не вызывает утечку памяти:

public static bool IsFileReady(this string sFilename) 
{ 
    try 
    { 
     using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None)) 
      return inputStream.Length > 0; 
    } 
    catch (Exception) 
    { 
     return false; 
    } 
} 

SpinWait.SpinUntil(yourFileName.IsFileReady); 
Смежные вопросы