2015-07-10 202 views
4

我有一個應用程序,它處理和重新大小的圖像,並偶爾在長時間迭代我得到OutOfMemoryException異常。內存不足異常System.Drawing.Image.FromStream()

我將我的圖像作爲文件流存儲在數據庫中,在處理過程中我需要將它們保存到臨時物理位置。

我的模型:

[Table("Car")] 
public class Car 
{ 
    [... some fields ...] 
    public virtual ICollection<CarPhoto> CarPhotos { get; set; } 
} 

[Table("CarPhoto")] 
public class CarPhoto 
{ 
    [... some fields ...] 
    public Guid Key { get; set; } 

    [Column(TypeName = "image")] 
    public byte[] Binary { get; set; } 
} 

處理看起來大致是這樣的:

foreach (var car in cars) 
{ 
    foreach (var photo in car.CarPhotos) 
    { 
     using (var memoryStream = new MemoryStream(photo.Binary)) 
     { 
      using (var image = Image.FromStream(memoryStream)) // this is where the exception is thrown 
      { 
       var ratioX = 600.00/image.Width; 

       var newWidth = (int)(image.Width * ratioX); 
       var newHeight = (int)(image.Height * ratioX); 

       using (var bitmap = new Bitmap(newWidth, newHeight)) 
       { 
        Graphics.FromImage(bitmap).DrawImage(image, 0, 0, newWidth, newHeight); 
        bitmap.Save(directory + filePath); 
       } 
      } 
     } 
    } 
} 

我已經看了this類似的線程,但沒有一個答案似乎我的情況下適用。

其中一個答案建議使用Image.FromStream(),但這正是我所做的。

我非常有信心,我的所有圖片都是有效的。異常似乎是隨機發生的,大多數情況下是處理較大的文件,但它也適用於較小的文件。有時候一個圖像會失敗,但下次處理會很好。

就我所見,我正確地處理了一切(內存流,圖像和位圖)。

這個工作正在被Hangfire激怒。這可能會導致問題嗎?

+0

我想你需要讓自己熟悉windbg並分析故障轉儲以找到解決方案。 – weismat

+0

處理前請勿將所有圖像加載到內存中。 FILESTREAM存儲的優點是您可以取回流而不是BLOB。使用SqlFileStream一次加載和處理一個圖像。 –

+0

Graphics對象也需要處理,使用*使用*就像您對其他對象所做的一樣。這並不能解決問題,並且由於地址空間碎片導致程序無法正常工作,因此位圖非常困難,因爲它們通常非常大。強烈建議在64位模式下運行此代碼。如果這不是一個選項,那麼重新使用MemoryStream對象,而不是重複重新創建它將是明智的。 –

回答

6

你的程序吃了很多的內存,得到OOM肯定不是意外。具體到哪裏死都是不可預測的。但是,是的,創建位圖是它可能首先死亡的地方。解決最可能的原因依次是:

foreach (var car in cars) 

沒有明顯的上界上的汽車你處理數量。每輛車都有多個圖像,並且您似乎將這些圖像存儲在內存中(photos.Binary)。換句話說,這個計劃是保證早晚死掉,只是因爲它需要處理越來越多的汽車。只有通過連續處理汽車而不是將它們全部存儲在內存中才能取得成功。可能是不愉快的建議,強烈建議在64位模式下運行此代碼。

using (var memoryStream = new MemoryStream(photo.Binary)) 

該內存流是一個大問題,其基礎緩衝區很可能存儲在大對象堆中。 LOH沒有被壓縮,反覆重新分配MemoryStream使得這個堆可能分散。你遲早會鑽出一個足夠大的孔來適應下一張照片的緩衝區。完全是隨機的,究竟何時發生取決於您之前處理的照片類型。您將需要重新使用該對象,而不是一遍又一遍地重新分配它。在進入循環之前只需創建一次,將容量設置爲一個不錯的大數字。

Graphics.FromImage(bitmap).DrawImage(image, 0, 0, newWidth, newHeight); 

該圖形對象需要處置。使用使用聲明就像您對其他對象所做的一樣。


總的來說,與您的程序,真正的問題是,它根本就沒有規模,並會一直倒下時,它需要應對不斷增加的數據集。修復這可能是一個非常重要的重寫,你幾乎肯定希望翻轉忽略位,並利用64位進程中的可用地址空間。硬件來拯救,它現在很容易獲得。

+0

感謝您的全面解答。我喜歡重新使用內存流的想法。從來沒有想過重新分配它可能是一個很大的問題。 – Jerry

0

可避免只用做所有的位圖碼(可以解決內存問題):

var resized = image.GetThumbnailImage(newWidth, newHeight,() => false, IntPtr.Zero); 
resized.Save(directory + filePath); 
+0

我試過了 - 有意義的是不要不必要地創建Bitmap和Graphics對象,但最後我得到了相同的異常。似乎我需要聽從漢斯的回答。 – Jerry

0

我有同樣的problem.The問題是圖像對象不一樣,如果你立刻處理甚至使用通過statement.For例如

IEnumerable<Control> mcontrols = this.panel1.Controls.Cast<Control>().Where(n => n.GetType() == typeof(PreviewImage)); 
     // for unclear reason after the dispose needs many times to clear everything; 
     while (mcontrols.Count() != 0) 
     { 
      foreach (PreviewImage pi in mcontrols) 
      { 
       pi.Dispose(); 
      } 
      mcontrols = this.panel1.Controls.Cast<Control>().Where(n => n.GetType() == typeof(PreviewImage)); 
      if (mcontrols == null) { 
       break; 
      } 

     } 

甚至第一圈還是在我PANEL1後,我還沒有desposed controls.Yes這是可怕的解決方法,但它爲我工作