Ich habe eine kleine WPF-Anwendung mit 2 Threads geschrieben - der Hauptthread ist der GUI-Thread und ein weiterer Thread ist der Worker.
App hat ein WPF-Formular mit einigen Steuerelementen. Es gibt eine Schaltfläche, mit der man ein Verzeichnis auswählen kann. Nach der Auswahl des Verzeichnisses, Anwendung scannt für .jpg-Dateien in diesem Verzeichnis und prüft, ob ihre Thumbnails in hashtable sind. wenn sie sind, es tut nichts. sonst ist es ihre vollen Dateinamen in die Warteschlange für Arbeiter hinzufügen.
Worker nimmt Dateinamen aus dieser Warteschlange, lädt JPEG-Bilder (unter Verwendung von WPF's JpegBitmapDecoder und BitmapFrame), erstellt Miniaturbilder von ihnen (unter Verwendung von WPF's TransformedBitmap) und fügt sie der Hashtabelle hinzu.
Alles funktioniert einwandfrei, außer dem Speicherverbrauch dieser Anwendung bei der Erstellung von Miniaturansichten für große Bilder (z. B. 5000x5000 Pixel). Ich habe Textfelder auf meinem Formular hinzugefügt, um den Speicherverbrauch zu zeigen (GC.GetTotalMemory() und Process.GetCurrentProcess().PrivateMemorySize64) und war sehr überrascht, denn GC.GetTotalMemory() bleibt in der Nähe von 1-2 MByte, während die Größe des privaten Speichers ständig wächst, vor allem beim Laden neuer Bilder (~ +100 MB pro Bild).
Selbst nach dem Laden aller Bilder, dem Erstellen von Thumbnails und dem Freigeben der Originalbilder bleibt die Größe des privaten Speichers bei etwa 700-800 MByte. Meine VirtualBox ist auf 512 MB physischen Speicher beschränkt, und Windows in VirtualBox fängt an, viel zu swappen, um diesen enormen Speicherverbrauch zu bewältigen. Ich vermute, dass ich etwas falsch mache, aber ich weiß nicht, wie ich dieses Problem untersuchen soll, denn laut GC ist die Größe des zugewiesenen Speichers sehr gering.
Anhängen des Codes der Thumbnail-Loader-Klasse:
class ThumbnailLoader
{
Hashtable thumbnails;
Queue<string> taskqueue;
EventWaitHandle wh;
Thread[] workers;
bool stop;
object locker;
int width, height, processed, added;
public ThumbnailLoader()
{
int workercount,i;
wh = new AutoResetEvent(false);
thumbnails = new Hashtable();
taskqueue = new Queue<string>();
stop = false;
locker = new object();
width = height = 64;
processed = added = 0;
workercount = Environment.ProcessorCount;
workers=new Thread[workercount];
for (i = 0; i < workercount; i++) {
workers[i] = new Thread(Worker);
workers[i].IsBackground = true;
workers[i].Priority = ThreadPriority.Highest;
workers[i].Start();
}
}
public void SetThumbnailSize(int twidth, int theight)
{
width = twidth;
height = theight;
if (thumbnails.Count!=0) AddTask("#resethash");
}
public void GetProgress(out int Added, out int Processed)
{
Added = added;
Processed = processed;
}
private void AddTask(string filename)
{
lock(locker) {
taskqueue.Enqueue(filename);
wh.Set();
added++;
}
}
private string NextTask()
{
lock(locker) {
if (taskqueue.Count == 0) return null;
else {
processed++;
return taskqueue.Dequeue();
}
}
}
public static string FileNameToHash(string s)
{
return FormsAuthentication.HashPasswordForStoringInConfigFile(s, "MD5");
}
public bool GetThumbnail(string filename,out BitmapFrame thumbnail)
{
string hash;
hash = FileNameToHash(filename);
if (thumbnails.ContainsKey(hash)) {
thumbnail=(BitmapFrame)thumbnails[hash];
return true;
}
AddTask(filename);
thumbnail = null;
return false;
}
private BitmapFrame LoadThumbnail(string filename)
{
FileStream fs;
JpegBitmapDecoder bd;
BitmapFrame oldbf, bf;
TransformedBitmap tb;
double scale, dx, dy;
fs = new FileStream(filename, FileMode.Open);
bd = new JpegBitmapDecoder(fs, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
oldbf = bd.Frames[0];
dx = (double)oldbf.Width / width;
dy = (double)oldbf.Height / height;
if (dx > dy) scale = 1 / dx;
else scale = 1 / dy;
tb = new TransformedBitmap(oldbf, new ScaleTransform(scale, scale));
bf = BitmapFrame.Create(tb);
fs.Close();
oldbf = null;
bd = null;
GC.Collect();
return bf;
}
public void Dispose()
{
lock(locker) {
stop = true;
}
AddTask(null);
foreach (Thread worker in workers) {
worker.Join();
}
wh.Close();
}
private void Worker()
{
string curtask,hash;
while (!stop) {
curtask = NextTask();
if (curtask == null) wh.WaitOne();
else {
if (curtask == "#resethash") thumbnails.Clear();
else {
hash = FileNameToHash(curtask);
try {
thumbnails[hash] = LoadThumbnail(curtask);
}
catch {
thumbnails[hash] = null;
}
}
}
}
}
}