2010-02-05 6 views
6

Aquí es mi clase Picture.cs:Obtener un 'fuera de la memoria' excepción en este relativamente sencillo programa

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.IO; 
using System.Drawing; 

namespace SharpLibrary_MediaManager 
{ 
    public class Picture:BaseFile 
    { 
     public int Height { get; set; } 
     public int Width { get; set; } 
     public Image Thumbnail { get; set; } 

     /// <summary> 
     /// Sets file information of an image from a given image in the file path. 
     /// </summary> 
     /// <param name="filePath">File path of the image.</param> 
     public override void getFileInformation(string filePath) 
     { 
      FileInfo fileInformation = new FileInfo(filePath); 
      if (fileInformation.Exists) 
      { 
       Name = fileInformation.Name; 
       FileType = fileInformation.Extension; 
       Size = fileInformation.Length; 
       CreationDate = fileInformation.CreationTime; 
       ModificationDate = fileInformation.LastWriteTime; 
       Height = calculatePictureHeight(filePath); 
       Width = calculatePictureWidth(filePath);     
      } 
     } 

     public override void getThumbnail(string filePath) 
     {    
      Image image = Image.FromFile(filePath); 
      Thumbnail = image.GetThumbnailImage(40, 40, null, new IntPtr());    
     } 

     private int calculatePictureHeight(string filePath) 
     { 
      var image = Image.FromFile(filePath); 
      return image.Height; 
     } 

     private int calculatePictureWidth(string filePath) 
     { 
      var image = Image.FromFile(filePath); 
      return image.Width; 
     } 
    } 
} 

Y aquí, estoy usando esa clase para extraer información de cada archivo en una carpeta determinada:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Windows.Forms; 
using System.IO; 

namespace SharpLibrary_MediaManager 
{ 
    public partial class Form1 : Form 
    { 
     public Form1() 
     { 
      InitializeComponent(); 
     } 

     string folderPath = @"D:\Images\PictureFolder"; 

     private void button1_Click(object sender, EventArgs e) 
     { 
      DirectoryInfo folder = new DirectoryInfo(folderPath); 
      List<Picture> lol = new List<Picture>(); 
      foreach (FileInfo x in folder.GetFiles()) 
      { 
       Picture picture = new Picture(); 
       picture.getFileInformation(x.FullName); 
       lol.Add(picture); 
      } 

      MessageBox.Show(lol[0].Name); 
     } 
    } 
} 

Recibo una excepción de falta de memoria y realmente no sé por qué. Esta es la primera vez que hago algo así, por lo que soy bastante nuevo en el procesamiento de archivos por lotes, etc.

¿Alguna ayuda, chicos? :)

Editar: me abrió el Administrador de tareas para ver el uso de memoria y cuando se presiona el botón para ejecutar el método noto mis aumenta el uso de memoria de 100 MB ~ cada segundo.

Editar 2: En mi carpeta tengo alrededor de 103 imágenes, cada imagen es ~ 100kb. Necesito una solución donde no importa cuántas imágenes hay en una carpeta. Alguien me recomendó abrir una imagen, hacer mi magia y luego cerrarla. Realmente no entiendo lo que él quiso decir con "cerca".

¿Alguien puede recomendar un enfoque diferente? :)

Datos 3: Aún así conseguir la excepción de memoria insuficiente, he cambiado el código en Picture.cs basado en las recomendaciones, pero estoy fuera de ideas. ¿Alguna ayuda?

public override void getFileInformation(string filePath) 
     { 
      FileInfo fileInformation = new FileInfo(filePath); 

      using (var image = Image.FromFile(filePath)) 
      { 
       if (fileInformation.Exists) 
       { 
        Name = fileInformation.Name; 
        FileType = fileInformation.Extension; 
        Size = fileInformation.Length; 
        CreationDate = fileInformation.CreationTime; 
        ModificationDate = fileInformation.LastWriteTime; 
        Height = image.Height; 
        Width = image.Width; 
       } 
      } 
     } 

Además, ¿debería abrir una nueva pregunta ahora que esta ha crecido un poco?

+3

¿Puede darnos un rastro de pila de la excepción OOM? – Steven

+0

¿Cómo puedo encontrar eso entonces lo proporciono? –

+0

Una excepción lanzada tiene un seguimiento de pila, pero no importa que el problema ya se haya detectado en los métodos calculatePictureHeight y calculatePictureWidth. – Steven

Respuesta

14

No está llamando Eliminar sus instancias de imagen. También crea tu imagen una vez y luego extrae tus datos.

Consulte también:

http://msdn.microsoft.com/en-us/library/8th8381z.aspx

EDITAR Si copiar el código y lo probó con mi biblioteca de imágenes. Mi promedio FileSize es de 2-3 MB por archivo. He ejecutado su programa e hizo exactamente lo que debería. El GC hizo exactamente lo que esperaba.

La memoria de su programa siempre fue de aproximadamente 11-35 MB. Conjunto de trabajo privado, el tamaño de la transacción fue estable en 43 MB.

He abortado el programa después de 1156 archivos con un tamaño de imagen total de 2.9 GB.

Debe haber otro motivo para la excepción de falta de memoria.

Aquí está mi salida del programa y el código:

1133: Total Size = 2.842,11 MB 
1134: Total Size = 2.844,88 MB 
1135: Total Size = 2.847,56 MB 
1136: Total Size = 2.850,21 MB 
1137: Total Size = 2.853,09 MB 
1138: Total Size = 2.855,86 MB 
1139: Total Size = 2.858,59 MB 
1140: Total Size = 2.861,26 MB 
1141: Total Size = 2.863,65 MB 
1142: Total Size = 2.866,15 MB 
1143: Total Size = 2.868,52 MB 
1144: Total Size = 2.870,93 MB 
1145: Total Size = 2.873,64 MB 
1146: Total Size = 2.876,15 MB 
1147: Total Size = 2.878,84 MB 
1148: Total Size = 2.881,92 MB 
1149: Total Size = 2.885,02 MB 
1150: Total Size = 2.887,78 MB 
1151: Total Size = 2.890,57 MB 
1152: Total Size = 2.893,55 MB 
1153: Total Size = 2.896,32 MB 
1154: Total Size = 2.898,92 MB 
1155: Total Size = 2.901,48 MB 
1156: Total Size = 2.904,02 MB 

código fuente:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.IO; 
using System.Drawing; 

namespace SharpLibrary_MediaManager 
{ 
    public abstract class BaseFile 
    { 
     public string Name { get; set; } 
     public string FileType { get; set; } 
     public long Size { get; set; } 
     public DateTime CreationDate { get; set; } 
     public DateTime ModificationDate { get; set; } 

     public abstract void getFileInformation(string filePath); 

    } 


    public class Picture : BaseFile 
    { 
     public int Height { get; set; } 
     public int Width { get; set; } 
     public Image Thumbnail { get; set; } 

     public override void getFileInformation(string filePath) 
     { 
      FileInfo fileInformation = new FileInfo(filePath); 

      using (var image = Image.FromFile(filePath)) 
      { 
       if (fileInformation.Exists) 
       { 
        Name = fileInformation.Name; 
        FileType = fileInformation.Extension; 
        Size = fileInformation.Length; 
        CreationDate = fileInformation.CreationTime; 
        ModificationDate = fileInformation.LastWriteTime; 
        Height = image.Height; 
        Width = image.Width; 
        Thumbnail = image.GetThumbnailImage(40, 40, null, new IntPtr()); 
       } 
      } 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      string folderPath = @"C:\Users\arthur\Pictures"; 

      DirectoryInfo folder = new DirectoryInfo(folderPath); 
      List<Picture> lol = new List<Picture>(); 
      double totalFileSize = 0; 
      int counter = 0; 
      foreach (FileInfo x in folder.GetFiles("*.jpg", SearchOption.AllDirectories)) 
      { 
       Picture p = new Picture(); 
       p.getFileInformation(x.FullName); 
       lol.Add(p); 
       totalFileSize += p.Size; 
       Console.WriteLine("{0}: Total Size = {1:n2} MB", ++counter, totalFileSize/1048576.0); 
      } 

      foreach (var p in lol) 
      { 
       Console.WriteLine("{0}: {1}x{2} px", p.Name, p.Width, p.Height); 
      } 
     } 
    } 
} 
1

Cuántas imágenes tiene en esa carpeta. Veo que recorre todas las imágenes y las carga en memoria directamente y las almacena en una lista. Puede ser la causa.

+0

Parece que lo que intenta hacer, al menos, es recorrerlos, obtener altura, ancho y miniaturas, y simplemente almacenarlos. Por lo tanto, cada imagen real solo debe estar en la memoria mientras se calculan. –

+0

Sí, eso es correcto. –

1

¿Qué tan grandes son sus archivos de imágenes? ¿Cuántos archivos hay en tu directorio? Dependiendo de esos parámetros pude ver cómo podría obtener una OutOfMemory. Es posible que desee volver a verificar su enfoque. En lugar de cargar todo en la memoria, debe cargar cada imagen individualmente, realizar su acción y luego continuar (después de que haya eliminado su imagen anterior, por supuesto).

+0

Por favor, mira mi edición. :) –

2

No debe almacenar todas las imágenes en una sola lista, debe hacer lo que tiene con una imagen a la vez y desechar cada imagen después de cada iteración.

Compruebe el método Image.Dispose().

+0

¿Cómo puedo disponer de una clase instanciada? –

+0

puede usar una instrucción de uso, como usar (Imagen p = nueva Imagen()) {// hacer trabajo aquí}, luego se llama al método de disposición al final de los corchetes, o puede llamar al método Dispose() de la imagen. – Pedro

5

Unos problemas que veo en el bate. En primer lugar, está cargando cada imagen dos veces con sus llamadas subsiguientes a CalculatePictureWidth y CalculatePictureHeight. En segundo lugar, en realidad nunca haces nada con la miniatura, parece. En tercer lugar, debe llamar a Dispose en las instancias de Image una vez que haya terminado de recopilar información de ellas.

+0

Puedes ver mi edición. –

+0

Bueno, sin ver la excepción StackTrace of the Out of Memory (cuando la excepción entra en el IDE, debería poder hacer clic en la ventana CallStack (Depurar-> Windows-> CallStack), la única otra cosa que noto es que usted está obteniendo el FileInfo, pasando por cada archivo, pasando el nombre a otra función y luego obteniendo el mismo FileInfo nuevamente. Eso podría simplificarse, pero no creo que esté causando su problema. –

3

El archivo de imagen contiene un identificador para un bloque grande de datos no administrados. Esta información debe eliminarse y esto se hace llamando explícitamente a Image.Dispose. Como no llama a Dispose en los métodos calculatePictureHeight y calculatePictureWidth, los archivos de imagen se guardan en la memoria, lo que está causando las excepciones de OOM.

[Actualización]: Tal vez un poco más de fondo es útil, por qué esto realmente está sucediendo. Después de todo, ¿no está el GC para limpiar este desastre para nosotros? Sí, lo hay, y el GC es bastante efectivo. Eventualmente también limpiará objetos que tuvimos que eliminar.

La clase de imagen contiene un finalizador. Este finalizador garantizará que se eliminen todos los recursos, incluso si olvida llamar a Dispose. Sin embargo, lo que ocurre aquí es que se queda sin memoria, lo que activa inmediatamente un GC.Collection. Durante esta recopilación, el GC encuentra todas sus imágenes sin referencia. Sin embargo, dado que el GC no ejecutará el método del finalizador de inmediato, todos sus objetos se promocionan a Gen1 (se guardan en la memoria) y también lo son todos sus recursos (la memoria nativa). Entonces, incluso después de que el GC ha comenzado, todavía hay muy poca memoria y se obtiene una excepción de OOM.

Es por esto que siempre debe desechar los objetos que implementen IDisposable.

6

Debe liberar los recursos utilizados al abrir un objeto Image.

O se puede llamar a botar, o crear su imagen en un comunicado Using

por ejemplo,

public override void getThumbnail(string filePath) 
{ 
    using (Image image = Image.FromFile(filePath)) 
    { 
     Thumbnail = image.GetThumbnailImage(40, 40, null, new IntPtr()); 
    } 
} 

Y como su clase contiene un Image que debe implementar la interfaz IDisposable, por lo que también se puede utilizar en un comunicado using.

+0

No veo el 'uso 'declaración que se usa con mucha frecuencia. ¿Es solo que no se conoce bien? – FrustratedWithFormsDesigner

+2

Lo uso en todas partes que puedo (es decir,cuando la clase objetivo es 'IDisposable' y mi flujo de programa lo permite). No tiene que jugar con 'Dispose', y está seguro de que su gestión de memoria se realiza correctamente. – Shimrod

+0

Esto lo arregló para mí. – sauv0168

1

Intente mirar sus imágenes de a una por vez. He encontrado ciertas causas de .jpg esto - las bibliotecas de manipulación de imágenes tienen problemas con ciertas imágenes, no estoy seguro de por qué. Pero hemos tenido ciertos .jpg guardado de photoshop de alguna manera extraña causa esto. Apuesto a que encontrarás que es una mala manzana.

-1

Varias personas mencionaron deshacerse de las llamadas una vez que se realizaron las imágenes. Sin embargo, esto no es suficiente y algo así como.Net Framework lo hará por usted a medida que aumenta el uso de la memoria. Como ha agregado cada imagen a una lista vinculada, el .NET Framework no puede deshacerse de ella, ya que implícitamente mantiene una referencia.

Esto significa que todos los datos almacenados dentro de la clase de imagen se mantienen durante el ciclo foreach. Desde el fragmento de código no puedo ver qué es, puede incluir una versión comprimida o incluso una versión sin comprimir de las imágenes que podría explicar la explosión de su memoria.

[Editar] eliminó una parte de un elemento que otros han señalado como irrelevante; Supuse que la clase de fotografía sería la clase de fotografía XNA.

+0

No estoy usando XNA, mi clase simplemente se llama Picture. –

+0

Las imágenes a las que me refería cuando hablé sobre su eliminación fueron las imágenes reales, no las miniaturas. Así que creo (pero debo estar equivocado) que no se guarda ninguna referencia y por lo tanto, que disponer de ellos será suficiente. Los únicos que se conservarán son los miniaturas, y es por eso que le aconsejé que implementara 'IDisposable' en su clase' Picture '. – Shimrod

+0

el framework llamará a la finalización cuando la recolección de basura sin embargo, mantiene una referencia a la imagen por lo que nunca se recogerá –

0

¿Se puede hacer esto?

public class Picture:BaseFile 
{ 
    public int Height { get; set; } 
    public int Width { get; set; } 
    public Image Thumbnail { get; set; } 

    /// <summary> 
    /// Sets file information of an image from a given image in the file path. 
    /// </summary> 
    /// <param name="filePath">File path of the image.</param> 
    public override void getFileInformation(string filePath) 
    { 
     FileInfo fileInformation = new FileInfo(filePath); 
     if (fileInformation.Exists) 
     { 
      /* 
      Name, FileType, Size, etc 
      */ 
      using (Image image = Image.FromFile(filePath)) 
      { 
       Height = image.Height; 
       Width = image.Width; 
       Thumbnail = image.GetThumbnailImage(40, 40, new Image.GetThumbnailImageAbort(ThumbnailCallback), default(IntPtr)); 
      } 
     } 
    } 

    public bool ThumbnailCallback() 
    { 
     return false; 
    } 
} 
0

Creo que el problema es:

Si se llama a la GetThumbnailImage con uno de los argumentos de anchura o altura estando -1, se generará un OutOfMemoryException también.

o

Si crea un objeto imagen con el Image.FromStream (por ejemplo, un MemoryStream) y luego cerrar la secuencia (MSDN dice claro que no se debe hacer durante la vida útil del objeto) y luego llamar a este método (GetThumbnailImage) en el objeto arrojará una OutOfMemoryException.

Cuestiones relacionadas