Out-of-mem-Exception в c # GUI при использовании FileSystemWatcher - многопоточность

c# multithreading image out-of-memory filesystemwatcher

276 просмотра

2 ответа

Я создаю приложение для windows-форм, в котором я (пытаюсь) делать обширные вычисления для изображений, когда они создаются в определенном каталоге, который я наблюдаю с помощью 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, а не потоком пользовательского интерфейса.

Я пробовал такие вещи, как:

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

К сожалению, никто из них не работал надежно. 1-й совсем нет. Второй работает только время от времени и третий тоже.

Некоторые из вас знают, что там происходит? Что я могу сделать, чтобы он работал правильно? Спасибо!

РЕДАКТИРОВАТЬ: Спасибо за комментарии: я также пытался вызывать GC.collect () для каждого события и пытался включать использование () и dispose (), где я могу. Когда я делаю процесс вручную (с помощью кнопок), он работает даже при обработке большого количества файлов один за другим. Но когда это делается с помощью обработчика событий, я иногда получаю исключение outOfMem-Exception даже для самого первого файла, который я копирую в папку. Файл всегда один и тот же BMP с 32 МБ. Это использование памяти для обработки одного изображения: введите описание изображения здесь

РЕДАКТИРОВАТЬ 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();
        }

    }
}

Заранее спасибо за дальнейшие подсказки :)

Автор: CaptIglu Источник Размещён: 08.11.2019 11:17

Ответы (2)


1 плюс

Решение

РЕШИТЬ:

Кажется, проблема в том, что событие запускается немедленно, но файл еще не окончательно скопирован. Это означает, что нам нужно подождать, пока файл освободится. Thread.Sleep (100) в начале события выполняет свою работу. Теперь, когда я знаю, что искать в Google, я нашел две ссылки: Это и это, где вы можете найти:

Событие OnCreated возникает, как только файл создан. Если файл копируется или передается в отслеживаемый каталог, событие OnCreated будет вызвано немедленно, за которым следует одно или несколько событий OnChanged.

Итак, что лучше всего подходит для моего случая, так это включить метод для проверки, заблокирован ли файл, и затем подождать в начале события для разблокировки файла. Нет необходимости в дополнительной теме или 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;
}

Тем не менее: Спасибо за вашу помощь ... это заставило меня на правильном пути;)

Автор: CaptIglu Размещён: 21.08.2016 06:19

0 плюса

Как MSDN говорит о FileSystemWatcher :

Обычные операции файловой системы могут вызывать более одного события. Например, когда файл перемещается из одного каталога в другой, могут возникнуть несколько событий OnChanged и некоторые события OnCreated и OnDeleted. Перемещение файла - это сложная операция, которая состоит из нескольких простых операций, поэтому вызывает несколько событий. Аналогично, некоторые приложения (например, антивирусное программное обеспечение) могут вызывать дополнительные события файловой системы, которые обнаруживаются FileSystemWatcher.

Может быть, ваше изображение загружается несколько раз.

Чтобы проверить это, вы можете добавить эту строку после imageFilepath = evtArgs.FullPath;

imageFilepath = evtArgs.FullPath;
Task.Run(()=>{MessageBox.Show(imageFilepath);});

Это проинформирует вас о том, что Createdсобытие запущено, и не будет задерживать вашу программу.

редактировать

Поместите строку кода, которая дает OutOfMemoryTry Catch. Как это и это вопросы описывает, вы можете получить эту ошибку , если ваш образ поврежден.

Автор: Stef Geysels Размещён: 20.08.2016 09:02
Вопросы из категории :
32x32