web-dev-qa-db-ja.com

プロセスによってファイルが解放されるのを待ちます

ss.Save()が新しいファイルで上書きできるように、ファイルが解放されるのを待つにはどうすればよいですか。これを2回近く実行すると(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;
    }
32

このような関数はそれを行います:

public static bool IsFileReady(string filename)
{
    // 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))
            return inputStream.Length > 0;
    }
    catch (Exception)
    {
        return false;
    }
}

whileループに貼り付けると、ファイルがアクセス可能になるまでブロックするものがあります。

public static void WaitForFile(string filename)
{
    //This will lock the execution until the file is ready
    //TODO: Add some logic to make it async and cancelable
    while (!IsFileReady(filename)) { }
}
60
Gordon Thompson

ファイルに書き込む前にアクセスをチェックすると、他のプロセスが書き込みを管理する前に再びアクセスを奪う可能性があります。そのため、次の2つのうちの1つを提案します。

  1. 他のエラーを隠さない再試行スコープで、やりたいことをラップします
  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、name)、contents));

14
Almund

特定のハンドル/ファイルシステムの場所が書き込み可能になるまで待機できる機能はありません。悲しいことに、できることはハンドルをポーリングして書くことだけです。

3
JaredPar

これは、一部のユーザーにとってはやり過ぎかもしれないソリューションです。ファイルのコピーが完了したときにのみトリガーされるイベントを持つ新しい静的クラスを作成しました。

ユーザーは、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);
}
3
Matt Williams
bool isLocked = true;
while (isLocked)
 try {
  System.IO.File.Move(filename, filename2);
  isLocked = false;
 }
 catch { }
 System.IO.File.Move(filename2, filename);
2
tsilb

プロセスが終了するまで、システムを待機させることができます。

これと同じくらい簡単です:

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

2
tojo

@Gordon Thompsonの答えを使用して、以下のコードのようなループを作成する必要があります。

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

CPUのオーバーヘッドを引き起こさない最適化された方法を見つけました。

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);
1
Davide Cannizzo

Dummy変数でlockステートメントを使用できますが、うまく機能しているようです。

こちら を確認してください。

0
David Refaeli

私は同様の答えを書きましたが、それは非同期、非ブロッキング、待機可能、キャンセル可能(タスクを停止するだけ)で、スローされた例外をチェックします。

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

この方法で使用できます:

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

本当に待つ必要がある場合、またはより多くのJS約束方法で:

IsFileReady(path).ContinueWith(t => File.Delete(path));
0
Letum