使用 FileSystemWatcher 时 c# GUI 中的内存不足异常 - 多线程

Out-of-mem-Exception in c# GUI when using FileSystemWatcher - multithreading

我构建了一个 windows-forms-app,每当图像在我使用 FileSystemWatcher 观察的特定目录中创建时,我(尝试)对图像进行大量计算。

private void OnNewFileInDir(object source, FileSystemEventArgs evtArgs) 
{  
  //Load the actual image:
  imageFilepath = evtArgs.FullPath;  //imageFilepath is a private class string var
  Image currentImage = Image.FromFile(imageFilepath);

  //Display the image in the picture box:
  UpdatePictureBox(currentImage);     //Method to update the GUI with invoking for the UI thread

  //Extensive Calculation on the images
  Image currentResultImage = DoExtensiveWork(currentImage);

  // Put the current result in the picture box
  UpdatePictureBox(currentResultImage );

  //dispose the current/temporary image
  currentImage.Dispose();
}

将新文件粘贴到目录时正确触发事件。但是我在

线上得到了一个"System.OutOfMemoryException"
Image currentImage = Image.FromFile(imageFilepath);

当我将此代码(使用相同的文件路径)准确地放入按钮事件(因此不使用 FileSystemWatcher)时,一切正常。所以我认为线程存在一些问题,因为大量计算随后由 FileSystemWatcher-Thread 而不是 UI 线程调用。

我试过类似的东西:

//TRY 1: By executing a button click method containg the code
pb_Calculate_Click(this, new EventArgs());    //This does not work eigther --> seems to be a problem with "Who is calling the method"

//TRY 2: Open a new dedicated thread for doing the work of the HistoCAD calculations
Thread newThread_OnNewFile = new Thread(autoCalcAndDisplay);
newThread_OnNewFile.Start();


//TRY 3: Use a background worker as a more safe threading method(?)
using (BackgroundWorker bw = new BackgroundWorker())
{
   bw.DoWork += new DoWorkEventHandler(bw_DoWork);
   if (bw.IsBusy == false)
   {
      bw.RunWorkerAsync();
   }
}

不幸的是 none 其中工作可靠。第一根本没有。第 2 只偶尔工作,第 3 次也是。

你们中有人知道那里发生了什么吗?我该怎么做才能使其正常工作?谢谢!

编辑: 感谢您的评论: 我还尝试在每个事件上调用 GC.collect() 并尝试尽可能包含 using() 和 dispose() 。当我手动(使用按钮)执行该过程时,即使一个接一个地处理大量文件,它也能正常工作。但是当使用事件处理程序完成时,我有时会在我复制到文件夹中的第一个文件上得到 outOfMem-Exception。文件始终是相同的 BMP,大小为 32MB。这是处理一张图像的内存使用情况:

编辑 2: 我创建了一个最小的示例(GUI,带有一个图片框和一个按钮样式的复选框)。事实证明,同样的事情正在发生。 OutOfMemException 发生在同一行(图片...)。特别是对于大型 BMP,异常几乎总是发生:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MinimalExampleTesting
{
    public partial class Form1 : Form
    {
        private string imageFilepath;
        private string autoModePath = @"C:\Users\Tim\Desktop\bmpordner";

        //Define a filesystem watcher object
        private FileSystemWatcher watcher;

        public Form1()
        {
            InitializeComponent();


            /*** Creating as FileSystemEventArgs watcher in order to monitor a specific folder ***/
            watcher = new FileSystemWatcher();
            Console.WriteLine(watcher.Path);
            // set the path if already exists, otherwise we have to wait for it to be set
            if (autoModePath != null)
                watcher.Path = autoModePath;
            // Watch for changes in LastAccess and LastWrite times and renaming of files or directories.
            watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
               | NotifyFilters.FileName | NotifyFilters.DirectoryName;
            // Only watch for BMP files.
            watcher.Filter = "*.bmp";
            // Add event handler. Only on created, not for renamed, changed or something
            // Get into the list of the watcher. Watcher fires event and "OnNewFileCreatedInDir" will be called
            watcher.Created += new FileSystemEventHandler(OnNewFileInDir);


        }

        private void tb_AutoMode_CheckedChanged(object sender, EventArgs e)
        {

            //First of all test if the auto mode path is set and correctly exists currently:
            if (!Directory.Exists(autoModePath) || autoModePath == null)
            {
                MessageBox.Show("Check if Auto Mode path is correctly set and if path exists",
                    "Error: Auto Mode Path not found");
                return;
            }

            // Begin watching if the AutoModePath was at least set
            if (autoModePath != null)
            {
                watcher.EnableRaisingEvents = tb_AutoMode.Checked;  //Since we have a toogle butten, we can use the 'checked' state to enable or disable the automode
            }

        }


        private void OnNewFileInDir(object source, FileSystemEventArgs evtArgs)
        {
            Console.WriteLine("New file in detected: " + evtArgs.FullPath);

            //Force a garbage collection on every new event to free memory and also compact mem by removing fragmentation.
            GC.Collect();

            //Set the current filepath in the class with path of the file added to the folder:
            imageFilepath = evtArgs.FullPath;

            //Load the actual image:
            Image currentImage = Image.FromFile(imageFilepath);

            UpdatePictureBox(currentImage);

        }

        private void UpdatePictureBox(Image img)
        {
            if (pictureBox_Main.InvokeRequired)
            {
                MethodInvoker mi = delegate
                {
                    pictureBox_Main.Image = img;
                    pictureBox_Main.Refresh();
                };
                pictureBox_Main.Invoke(mi);
            }
            else {  //Otherwise (when the calculation is perfomed by the GUI-thread itself) no invoke necessary
                pictureBox_Main.Image = img;
                pictureBox_Main.Refresh();
            }
            img.Dispose();
        }

    }
}

提前感谢您的进一步提示:)

正如 MSDN 所说 FileSystemWatcher:

Common file system operations might raise more than one event. For example, when a file is moved from one directory to another, several OnChanged and some OnCreated and OnDeleted events might be raised. Moving a file is a complex operation that consists of multiple simple operations, therefore raising multiple events. Likewise, some applications (for example, antivirus software) might cause additional file system events that are detected by FileSystemWatcher.

可能你的图片被加载了好几次。

为了测试它,你可以在imageFilepath = evtArgs.FullPath;

之后添加这一行
imageFilepath = evtArgs.FullPath;
Task.Run(()=>{MessageBox.Show(imageFilepath);});

这将通知您 Created 事件已触发,并且不会阻止您的程序。

编辑

将给出 OutOfMemory 的代码行放在 Try Catch 中。 如 this and this 问题所述,如果您的图像已损坏,您可能会遇到此错误。

已解决:

问题似乎是,该事件会立即触发,但文件尚未最终复制。这意味着我们必须等到文件可用。 事件开始时的 Thread.Sleep(100) 完成了这项工作。正如我现在知道 google 的用途,我找到了两个链接: This and this 您可以在哪里找到:

The OnCreated event is raised as soon as a file is created. If a file is being copied or transferred into a watched directory, the OnCreated event will be raised immediately, followed by one or more OnChanged events

因此,最适合我的情况是包含一种方法来测试文件是否仍处于锁定状态,而不是在事件开始时等待文件解锁。不需要额外的线程或 BackgroundWorker。 见代码:

private void OnNewFileInDir(object source, FileSystemEventArgs evtArgs)
{
   Console.WriteLine("New file detected: " + evtArgs.FullPath);

   //Wait for the file to be free
   FileInfo fInfo = new FileInfo(evtArgs.FullPath);
   while (IsFileLocked(fInfo))
   {
       Console.WriteLine("File not ready to use yet (copy process ongoing)");
       Thread.Sleep(5);  //Wait for 5ms
   }

   //Set the current filepath in the class with path of the file added to the folder:
   imageFilepath = evtArgs.FullPath;
   //Load the actual image:
   Image currentImage = Image.FromFile(imageFilepath);    
   UpdatePictureBox(currentImage);
}

private static bool IsFileLocked(FileInfo file)
{
    FileStream stream = null;
    try
    {
        //try to get a file lock
        stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
    }
    catch (IOException)
    {
       //File isn't ready yet, so return true as it is still looked --> we need to keep on waiting
       return true;
    }
    finally
    {
        if (stream != null){ 
            stream.Close();
            stream.Dispose();
        }
    }
    // At the end, when stream is closed and disposed and no exception occured, return false --> File is not locked anymore
    return false;
}

尽管如此:感谢您的帮助...它让我走上了正确的轨道;)