Wait for file to be freed by process

后端 未结 11 921
挽巷
挽巷 2020-12-02 14:23

How do I wait for the file to be free so that ss.Save() can overwrite it with a new one? If I run this twice close together(ish), I get a generic GDI+

相关标签:
11条回答
  • 2020-12-02 15:10

    Here is a solution that may be overkill for some users. I've created a new static class which has an event which is triggered only when the file finishes copying.

    The user registers files which they would like to watch by calling FileAccessWatcher.RegisterWaitForFileAccess(filePath). If the file is not already being watched a new task is started which repeatedly checks the file to see if it can be opened. Each time it checks it also reads the file size. If the file size does not increase in a pre-defined time (5 minutes in my example) the loop is exited.

    When the loop exits from the file being accessible or from the timeout the FileFinishedCopying event is triggered.

    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;
        }
    }
    

    Example usage:

    // 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);
    

    Handle the FileFinishedCopyingEvent:

    private void FileAccessWatcher_FileFinishedCopying(object sender, FileSystemEventArgs e)
    {
        Console.WriteLine("File finished copying: " + e.FullPath);
    }
    
    0 讨论(0)
  • 2020-12-02 15:11
    bool isLocked = true;
    while (isLocked)
     try {
      System.IO.File.Move(filename, filename2);
      isLocked = false;
     }
     catch { }
     System.IO.File.Move(filename2, filename);
    
    0 讨论(0)
  • 2020-12-02 15:12

    If you check access before writing to the file some other process might snatch the access again before you manage to do your write. Therefor I would suggest one of the following two:

    1. Wrap what you want to do in a retry scope that won't hide any other error
    2. Create a wrapper method that waits until you can get a stream and use that stream

    getting a stream

    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.");
    }
    

    then use it like this:

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

    retry scope

    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.");
    }
    

    and then use WithRetry(() => File.WriteAllText(Path.Combine(_directory, name), contents));

    0 讨论(0)
  • 2020-12-02 15:15

    You can let the System wait, until the process is closed.

    Just as simple as this:

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

    0 讨论(0)
  • 2020-12-02 15:16

    Taking the top answer I wrote a similar one, but it's async, non-blocking, awaitable, cancelable (just stop the task) and checks the exception thrown.

    public static async Task IsFileReady(string filename)
        {
            await Task.Run(() =>
            {
                if (!File.Exists(path))
                {
                    throw new IOException("File does not exist!");
                }
    
                var isReady = false;
    
                while (!isReady)
                {
                    // 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(filename, FileMode.Open, FileAccess.Read, FileShare.None))
                            isReady = inputStream.Length > 0;
                    }
                    catch (Exception e)
                    {
                        // Check if the exception is related to an IO error.
                        if (e.GetType() == typeof(IOException))
                        {
                            isReady = false;
                        }
                        else
                        {
                            // Rethrow the exception as it's not an exclusively-opened-exception.
                            throw;
                        }
                    }
                }
            });
        }
    

    You can use it in this fashion:

    Task ready = IsFileReady(path);
    
    ready.Wait(1000);
    
    if (!ready.IsCompleted)
    {
        throw new FileLoadException($"The file {path} is exclusively opened by another process!");
    }
    
    File.Delete(path);
    

    If you have to really wait for it, or in a more JS-promise-way:

    IsFileReady(path).ContinueWith(t => File.Delete(path));
    
    0 讨论(0)
提交回复
热议问题