2016-08-20 4 views
3

Я создаю приложение windows-forms-app, где я (стараюсь) выполнять обширные вычисления на изображениях всякий раз, когда они создаются в определенном каталоге, который я смотрю с помощью FileSystemWatcher.Out-of-mem-Exception в C# GUI при использовании 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) все работает отлично. Поэтому я подумал, что есть какая-то проблема в отношении потока, так как расширенный расчет затем вызывается файловым файлом ThreadSystemWatcher-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(); 
    } 
} 

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

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

EDIT: Спасибо за комментарии: Я также попытался вызвать GC.Collect() на каждом событии и попытались включить с помощью() и Dispose(), где я могу. Когда я делаю процесс вручную (с кнопками), он работает даже при обработке большого количества файлов один за другим. Но когда это делается с обработчиком событий, я иногда получаю outOfMem-Exception даже в самом первом файле, который я копирую в папке. Файл всегда тот же BMP с 32 МБ. Это использование памяти для обработки одного изображения: enter image description here

EDIT 2: я создал минимальный пример (GUI с одной картинной коробкой и один Checkbox в ButtonStyle). Оказывается, происходит то же самое. Исключение OutOfMemException происходило в одной строке (Изображение ...). Специально для больших ВМР исключение occours почти всегда:

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

    } 
} 

Спасибо заранее для дальнейших намеков :)

+0

'Изображение currentImage = Image.FromFile (imageFilepath)' не имеет ничего общего с поток пользовательского интерфейса. Если бы вы вместо этого получили бы «System.InvalidOperationException». Я думаю, что ваша проблема скорее связана с тем, что ваш образ не полностью написан до того, как вы его прочитали. –

+0

Можете ли вы показать, как вы создаете FileSystemWatcher? – Balah

+0

Класс Image - это особый класс .NET, который очень неумолимо забыть вызвать Dispose(). Он использует очень маленькую кучу GC и * lots * неуправляемой памяти. Очень плохо, потому что ваш код, похоже, не дает GC большой части тренировки, поэтому у вас нет его очистки для вас. Crystal Ball говорит, что ваш UpdatePictureBox() забывает распоряжаться старым PictureBox.Image. Если это не поможет, используйте профилировщик памяти или получите достаточно отчаянное количество, чтобы подсчитывать вызовы OnNewFileInDir() и вызывать GC.Collect() каждый раз, скажем, 100 раз. –

ответ

1

РЕШИТЬ:

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

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

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

+0

Поздравляем. Приятно видеть, что вы нашли это сами! –

0

В MSDN говорит о FileSystemWatcher:

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

Возможно, изображение загружено несколько раз.

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

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

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

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

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

+0

Я добавил строку, и она просто показывает правильный новый путь к файлу в окне сообщений. OutOfMemException по-прежнему происходит после этого – CaptIglu

+0

И вы получаете сообщение только один раз? –

+0

да, только один msgbox! Я прочитал первое сообщение раньше и тоже попробовал. Но я думаю, что это не так, потому что это формат изображения. Так как он работает, если вы делаете без FileSystemWatcher, например, в дополнительной кнопке. Спасибо, тем не менее! – CaptIglu