我用2个线程编写了很少的WPF应用程序 - 主线程是GUI线程,另一个线程是worker
App有一个带有一些控件的WPF表单。有一个按钮,允许选择目录。
选择目录后,应用程序将扫描该目录中的.jpg文件,并检查其缩略图是否在哈希表中。如果他们是,它什么都不做。否则它将他们的完整文件名添加到工作队列。
Worker正在从这个队列中获取文件名,加载JPEG图像(使用WPF的JpegBitmapDecoder和BitmapFrame),制作它们的缩略图(使用WPF的TransformedBitmap)并将它们添加到哈希表中。
一切正常,但在为大图像(如5000x5000像素)制作缩略图时,此应用程序的内存消耗除外。我在表单上添加了文本框以显示内存消耗(GC.GetTotalMemory()和Process.GetCurrentProcess()。PrivateMemorySize64)并且非常惊讶,因为GC.GetTotalMemory()保持接近1-2 MB,而私有内存大小不断增长,特别是在加载新图像时(每张图像约+ 100Mb)
即使在加载所有图像,制作它们的缩略图并释放原始图像之后,私有内存大小仍保持在~700-800Mbytes。我的VirtualBox仅限于512Mb的物理内存,VirtualBox中的Windows开始交换很多来处理这种巨大的内存消耗。我想我做错了什么,但我不知道如何调查这个问题,根据GC的说法,分配的内存大小非常低。
附加缩略图加载器类的代码:
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;
}
}
}
}
}
}
答案 0 :(得分:10)
我怀疑BitmapCacheOption.OnLoad
正在将图像添加到Framework的内存占用空间,但由于您不拥有图像缓存中的对象,因此它们不会出现在GC
方法调用的结果中。尝试使用BitmapCacheOption.None
代替,看看是否能解决您的内存问题。注意:这样做会对性能产生巨大影响。
答案 1 :(得分:3)
解决了这个问题 只需用BitmapCacheOption.None替换BitmapCacheOption.OnLoad:)
答案 2 :(得分:2)
我认为它与图像有关 - Image类的底层对象是非托管的,因为它们消耗的内存不包含在GC计数器中。
他们还需要特别注意你如何处理它们 - 它们的托管内存消耗非常低,因此GC并没有真正关注,但是无人管理的内存 - 你可以看到它。
底线 - 仅仅让它们超出范围是不够的,你必须在完成时明确地调用它们。