如何 dispose/release 文件被另一个进程使用?

How to dispose/release file being use by another process?

在更改事件中使用 filesystemwatcher 我正在使用 fileinfo 获取文件,然后将文件复制到新目录并在复制时继续复制文件并覆盖,直到文件更改结束:

private void Watcher_Changes(object sender, FileSystemEventArgs e)
{
    try
    {
        var info = new FileInfo(e.FullPath);
        var newSize = info.Length;
        
        string FileN1 = "File Name : ";
        string FileN2 = info.Name;
        string FileN3 = " Size Changed : From ";
        string FileN5 = "To";
        string FileN6 = newSize.ToString();
        
        Println(FileN1 + FileN2 + FileN3 + FileN5 + FileN6);
        
        CopyFileOnChanged(System.IO.Path.GetDirectoryName(e.FullPath), e.FullPath);
    }
    catch (Exception ex)
    {
        PrintErr(ex);
    }
}

和复制文件方法:

bool makeonce = false;
string NewFileName = "";
private void CopyFileOnChanged(string Folder, string FileName)
{
    if (makeonce == false)
    {
        string t = "";
        string fn = "";
        string locationToCreateFolder = Folder;
        string folderName;
        string date = DateTime.Now.ToString("ddd MM.dd.yyyy");
        string time = DateTime.Now.ToString("HH.mm tt");
        string format = "Save Game {0} {1}";
        folderName = string.Format(format, date, time);
        Directory.CreateDirectory(locationToCreateFolder + "\" + folderName);
        t = locationToCreateFolder + "\" + folderName;
        fn = System.IO.Path.GetFileName(FileName);
        NewFileName = System.IO.Path.Combine(t, fn);
        makeonce = true;
    }
    File.Copy(FileName, NewFileName, true);
}

问题是当它再次 File.Copy 时抛出异常文件正在被其他进程使用。

[+] File Name : New Text Document (2).txt Size Changed : From To662 At : 6/3/2022 3:56:14 PM [+] File Name : New Text Document (2).txt Size Changed : From To662 At : 6/3/2022 3:56:14 PM [-] System.IO.IOException: The process cannot access the file 'C:\Program Files (x86)\Win\Save Game Fri 06.03.2022 15.56 PM\New Text Document (2).txt' because it is being used by another process. at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.File.InternalCopy(String sourceFileName, String destFileName, Boolean overwrite, Boolean checkHost) at System.IO.File.Copy(String sourceFileName, String destFileName, Boolean overwrite) at Watcher_WPF.MainWindow.CopyFileOnChanged(String Folder, String FileName) in C:\Users\Chocolade1972\Downloads\Watcher_WPF-master\Watcher_WPF-master\Watcher_WPF\MainWindow.xaml.cs:line 356 at Watcher_WPF.MainWindow.Watcher_Changes(Object sender, FileSystemEventArgs e) in C:\Users\Chocolade1972\Downloads\Watcher_WPF-master\Watcher_WPF-master\Watcher_WPF\MainWindow.xaml.cs:line 258

第 258 行是:

CopyFileOnChanged(System.IO.Path.GetDirectoryName(e.FullPath), e.FullPath);

为简洁起见,我将只概述我在发票处理的专业设置中创建的解决方案,而不是为您提供完整的解决方案(我也不能,因为代码受版权保护)。

让开,我们开始吧:

我首先有一个“收件箱”文件夹,我有一个 FileSystemWatcher 手表。我对新文件做出了反应,但对于已更改的文件,效果完全相同。对于每个事件,我排队了一个项目:

private ConcurrentQueue<string> _queue = new ();

private void Watcher_Changes(object sender, FileSystemEventArgs e)
{
     _queue.Enqueue(e.FullPath);
}

这就是 EventHandler 所做的全部工作。 Objective这里是为了尽快处理来自FSW的事件。 否则你可能 运行 筋疲力尽,FSW 将丢弃事件!(是的,我是通过错误报告和大量汗水学到的)

实际 工作是在单独的线程中完成的,该线程消耗了队列。

// Just brief display of the concept.
// This function would be used as Thread run every 
// x Time, triggered by a Timer if the Thread is not still running.
private void MyWorkerRun()
{
    // My Input came in mostly in batches, so I ran until the queue was empty.
    // You may need to adapt to maybe only dequeue N Items for each run ... 
    // Whatever does the trick.
    //     while( _queue.Any() ) 
    //
    // Maybe only process the N amount of Items the Queue has at the
    //  start of the current run?
    var itemsToProcess = _queue.Count;
    if( itemsToProcess <= 0 ) return;
    for( int i = 0; i < itemsToProcess; i++)
    {
         string sourcePath = _queue.Dequeue(); // ConcurrentQueue is Thread-Safe
         // No file there anymore? Drop it.
         if(!File.Exists(sourcePath)) continue;
         
         // TODO Construct Target-Path
         string targetPath = GetTargetPath(sourcePath); // Just a dummy for this example...

         // Try to copy, requeue if failed.
         if(!TryCopy(sourcePath, targetPath))
         {
              // Requeue for later
              // It will be picked up in _next_ run,
              // so there should be enough time in between tries.
              _queue.Enqueue(sourcePath);
         }
    }
}

private bool TryCopy(string source, string target){ /* TODO for OP */ }


我必须补充一点,我在 前做过这个。今天我可能会考虑使用 TPL DataFlow 来为我处理排队和重新排队。


当然,您可以随时为它增添趣味。我尽量保持简单,同时清楚地展示概念。

后来我有更多的要求:比如程序再次启动时要能退出并从停止的地方继续。它应该只重试 X 次,然后将文件写入“死信箱”,然后添加更多处理步骤,如果队列超过 N 个条目,它应该向某个地址发送电子邮件……你明白了。如果需要,您可以随时使其变得更复杂。

t = locationToCreateFolder + "\" + folderName;

您的“locationToCreateFolder”是目录名称而不是路径。因为它来自这里 :

CopyFileOnChanged(System.IO.Path.GetDirectoryName(e.FullPath), e.FullPath);

因此,当您合并全局路径时无效:

NewFileName = System.IO.Path.Combine(t, fn);