Я создаю приложение 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 МБ. Это использование памяти для обработки одного изображения:
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();
}
}
}
Спасибо заранее для дальнейших намеков :)
'Изображение currentImage = Image.FromFile (imageFilepath)' не имеет ничего общего с поток пользовательского интерфейса. Если бы вы вместо этого получили бы «System.InvalidOperationException». Я думаю, что ваша проблема скорее связана с тем, что ваш образ не полностью написан до того, как вы его прочитали. –
Можете ли вы показать, как вы создаете FileSystemWatcher? – Balah
Класс Image - это особый класс .NET, который очень неумолимо забыть вызвать Dispose(). Он использует очень маленькую кучу GC и * lots * неуправляемой памяти. Очень плохо, потому что ваш код, похоже, не дает GC большой части тренировки, поэтому у вас нет его очистки для вас. Crystal Ball говорит, что ваш UpdatePictureBox() забывает распоряжаться старым PictureBox.Image. Если это не поможет, используйте профилировщик памяти или получите достаточно отчаянное количество, чтобы подсчитывать вызовы OnNewFileInDir() и вызывать GC.Collect() каждый раз, скажем, 100 раз. –