2010-03-31 103 views
3

我寫了2個線程的小WPF應用程序 - 主線程是GUI線程和另一個線程是工人。
應用程序有一個WPF窗體和一些控件。有一個按鈕,允許選擇目錄。 選擇目錄後,應用程序掃描該目錄中的.jpg文件並檢查其縮略圖是否在散列表中。如果是的話,它什麼都不做。否則它會將他們的完整文件名添加到工作隊列中。
工作人員正在從這個隊列中取文件名,加載JPEG圖像(使用WPF的JpegBitmapDecoder和BitmapFrame),製作它們的縮略圖(使用WPF的TransformedBitmap)並將它們添加到散列表。
一切工作正常,除了這個應用程序在製作大圖像(如5000x5000像素)的縮略圖時消耗的內存。我在表單上添加了文本框來顯示內存消耗(GC.GetTotalMemory()和Process.GetCurrentProcess()。PrivateMemorySize64),並且非常驚訝,cuz GC.GetTotalMemory()保持接近1-2 Mbytes,而私有內存大小不斷增長,特別是在加載新圖像時(每幅圖像〜100Mb)。
即使加載完所有圖像後,製作它們的縮略圖並釋放原始圖像,私有內存大小仍保留在〜700-800M字節。我的VirtualBox限於512Mb的物理內存,而VirtualBox中的Windows開始交換很多來處理這種巨大的內存消耗。我想我做錯了什麼,但我不知道如何調查這個問題,因爲GC根據GC分配的內存大小非常低。C#WPF應用程序使用太多的內存,而GC.GetTotalMemory()是低

附加縮略圖加載類的代碼:

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

聽起來像大型對象堆中正在發生的事情...當你處理圖像時,你能隨着時間的推移獲得性能指標嗎? LOH沒有采用與標準內存相同的方式,並且(如果我沒有記錯的話)更容易發生碎片... – LorenVS 2010-03-31 23:56:47

+1

這可能是WPF中的一個錯誤,它存在一些非託管內存的已知問題。我建議你在Connect(http://connect.microsoft.com/)上報告。如果內存泄漏對您的應用程序有問題,並且您無法修復,則可以將代碼的關鍵部分移至另一個進程或AppDomain。 – 2010-03-31 23:59:40

回答

3

解決了這個問題。
剛剛與BitmapCacheOption.None :)更換BitmapCacheOption.OnLoad

+2

@Dmitry:您應該將答案標記爲已接受的答案。如果這是對Nymaen答案的「回覆」,你應該將答案標記爲已接受的答案(點擊支票),並將其作爲評論,而不是作爲單獨的答案... – 2010-04-02 22:20:33

+0

我無法回覆Nymaen的回答因爲我比他寫下他的答案早。 – mephisto123 2012-11-22 01:51:10

2

我認爲這是與圖像 - 圖像類的基本對象是不受管理的,因爲這種記憶被他們消耗不包括在GC計數器。

他們還需要額外關心你如何處置它們 - 管理它們的內存消耗非常低,所以GC沒有真正關注,但是沒有內存 - 你可以看到它。

底線 - 讓它們超出範圍是不夠的,你必須在完成後明確地調用它們。

+1

BitmapImage類中沒有Dispose()方法。 – mephisto123 2010-04-01 00:30:05

10

我懷疑BitmapCacheOption.OnLoad被添加圖像框架的內存佔用,但因爲你沒有自己的圖像緩存中的對象,它們不會出現在GC方法調用的結果中。請嘗試使用BitmapCacheOption.None來查看是否解決了您的內存問題。注意:這樣做會對性能產生巨大影響。