2012-05-24 10 views
8

que estoy haciendo lo siguiente:StackOverflowException de inicialización lista grande

public static class DataHelper 
{ 
    public static List<Seller> Sellers = new List<Seller> { 
    {new Seller {Name = "Foo", Location = new LatLng {Lat = 12, Lng = 2}, Address = new Address {Line1 = "blah", City = "cokesville"}, Country = "UK"}, 
    //plus 3500 more Sellers 
    }; 
} 

Cuando tengo acceso a DataHelper.Sellers desde el interior de mi página web MVC, aparece un StackOverflowException. Cuando depuro con Visual Studio, la pila tiene solo media docena de marcos y no existe el signo obvio habitual de un desbordamiento de la pila (es decir, no se repiten las llamadas a métodos).

La llamada aplicación puede ser tan simple como esto para provocar la excepción:

public ActionResult GetSellers() 
{ 
    var sellers = DataHelper.Sellers; 
    return Content("done"); 
} 

Información adicional:

  • cuando corro el mismo código desde el interior de una prueba de unidad que está bien
  • si elimino la mitad de los vendedores (mitad superior o inferior), está bien en la aplicación web, por lo que no es un problema con ningún vendedor específico
  • I h Ave intentó cambiar vendedores de una propiedad y la lista de inicialización en la primera llamada - sin ayuda
  • También intentado añadir la mitad a una lista y medio a otro y la combinación de la 2 - de nuevo sin ayuda

Voy a ser muy impresionado por la respuesta correcta a esta!

+1

¿Cómo está accediendo a la lista? – Oded

+6

JEEEEZ, ¡realmente deberías considerar almacenarlos en otro lugar, codificando más de 3500 elementos! : | – mattytommo

+0

@Oded - algo así como var sellers = DataHelper.Sellers; – Gaz

Respuesta

7

Esto es causado por el hecho de que, efectivamente, su lista de inicialización en línea es demasiado grande para la pila - ver este muyrelated question over on the msdn forums donde el escenario está cerca-en idéntica.

Un método en .Net tiene una profundidad y un tamaño de pila. Un StackOverflowException es causado no solo por la cantidad de llamadas en la pila, sino por el tamaño total de la asignación de memoria de cada método en la pila. En este caso, su método es demasiado grande, y eso se debe a la cantidad de variables locales.

A modo de ejemplo, consideremos el siguiente código:

public class Foo 
    { 
      public int Bar { get; set;} 
    } 
    public Foo[] GetInts() 
    { 
     return new Foo[] { new Foo() { Bar = 1 }, new Foo() { Bar = 2 }, 
      new Foo() { Bar = 3 }, new Foo() { Bar = 4 }, new Foo() { Bar = 5 } }; 
    } 

Ahora mira el lead-in IL de ese método cuando se compila (esta es una versión de lanzamiento, también):

.maxstack 4 
.locals init (
    [0] class SomeExample/Foo '<>g__initLocal0', 
    [1] class SomeExample/Foo '<>g__initLocal1', 
    [2] class SomeExample/Foo '<>g__initLocal2', 
    [3] class SomeExample/Foo '<>g__initLocal3', 
    [4] class SomeExample/Foo '<>g__initLocal4', 
    [5] class SomeExample/Foo[] CS$0$0000 
) 

Nota: el bit real anterior a /, es decir, SomeExample dependerá del espacio de nombres y la clase en que se definan el método y la clase anidada. He tenido que editar varias veces para eliminar los nombres tipo de algún código en progreso que 'Estoy escribiendo ¡en el trabajo!

¿Por qué todos esos lugareños? Debido a la forma en que se realiza la inicialización en línea.Cada objeto se actualiza y se almacena en un local "oculto", esto es necesario para que las asignaciones de propiedades se puedan realizar en las inicializaciones en línea de cada Foo (la instancia del objeto es necesaria para generar la propiedad establecida para Bar). Esto también demuestra cómo la inicialización en línea es solo algo de azúcar sintáctico C#.

En su caso, son estos locales los que están causando que la pila explote (hay al menos unos miles de ellos solo para los objetos de nivel superior, pero también tiene iniciadores anidados).

El compilador de C# podía alternativamente pre-cargar el número de referencias requeridas para cada sobre de la pila (apareciendo cada uno fuera para cada asignación de propiedad), pero luego de que está abusando de la pila donde el uso de los locales llevará a cabo mucho mejor.

También podría utilizar una sola locales, ya que cada uno es meramente escrito demasiado y se almacena en la lista de índice de matriz, el local nunca se necesita de nuevo. Eso podría ser algo que el equipo de C# considere: Eric Lippert puede tener algunas ideas al respecto si tropieza con este hilo.

Ahora, este examen también nos da una vía potencial alrededor de este uso de los locales para el método muy masiva: utilizar un iterador:

public Foo[] GetInts() 
{ 
    return GetIntsHelper().ToArray(); 
} 

private IEnumerable<Foo> GetIntsHelper() 
{ 
    yield return new Foo() { Bar = 1 }; 
    yield return new Foo() { Bar = 2 }; 
    yield return new Foo() { Bar = 3 }; 
    yield return new Foo() { Bar = 4 }; 
    yield return new Foo() { Bar = 5 }; 
} 

Ahora, la IL para GetInts() ahora simplemente tiene .maxstack 8 en la cabeza, y no lugareños. En cuanto a la función de repetidor GetIntsHelper() tenemos:

.maxstack 2 
.locals init (
    [0] class SomeExample/'<GetIntsHelper>d__5' 
) 

Así que ahora hemos dejado de usar todos esos locales en esos métodos ...

Pero ...

vistazo a la clase SomeExample/'<GetIntsHelper>d__5', que ha sido generado automáticamente por el compilador, y vemos que los locales todavía están allí; se han promocionado a campos en esa clase:

.field public class SomeExample/Foo '<>g__initLocal0' 
.field public class SomeExample/Foo '<>g__initLocal1' 
.field public class SomeExample/Foo '<>g__initLocal2' 
.field public class SomeExample/Foo '<>g__initLocal3' 
.field public class SomeExample/Foo '<>g__initLocal4' 

Entonces, la pregunta es: ¿la creación de ese objeto también explotará si se aplica a su escenario? Probablemente no, porque en la memoria debería ser como intentar inicializar una gran matriz, donde las matrices de millones de elementos son bastante aceptables (suponiendo que hay suficiente memoria en la práctica).

Por lo tanto - que puede ser que poder fijar su código simplemente moviendo hacia el uso de un método que IEnumerableyield s cada elemento.

Pero la mejor práctica dice que si tiene que tener esto estáticamente definido, considere agregar los datos a un recurso o archivo incrustado en el disco (XML y Linq a XML podrían ser una buena opción) y luego cargarlo desde allí en demanda.

Mejor aún, pegarlo en una base de datos :)

+0

Gracias Andras. Ese enlace que publicas realmente no explica * por qué * - así que supongo que la respuesta es que nadie más que el equipo de CLR realmente * sabe * exactamente ... aún así es bueno encontrar a alguien más que tenga el mismo problema. No tengo ningún requisito para que esto se incruste, es simplemente una manera fácil de tratar con algunos datos de prueba durante la creación de prototipos. Entrará en una fuente externa de inmediato. – Gaz

+0

@Gaz ver mi respuesta actualizada - eso debería explicar por qué –

+0

¡genial! Gracias. Supongo que la parte clave de la información adicional para explicar por qué tengo el problema aquí es el argumento de Iván de que el tamaño de la pila de ASP.NET es 256K. Así que con referencias de 64 bits y 3500 objetos que me llevan justo por encima de ese límite. Curiosamente, probé su sugerencia de usar un iterador y esta vez no pude compilar porque decía que no había suficiente espacio de almacenamiento para crear el archivo pdb. Sin embargo, tengo 2 discos, uno con 1.5GB gratis y otro con aproximadamente 5GB. ¡Eso es un archivo pdb! (Editar: me doy cuenta de que la verdadera explicación sería más sutil ...) – Gaz

0

¿cómo se accede a DataHelper.Sellers en su Controlador? ¿Está utilizando GEt o POST para este controlador? Para una cantidad tan grande de datos, debe usar POST.

También es necesario comprobar el tamaño de pila de IIS: http://blogs.msdn.com/b/tom/archive/2008/03/31/stack-sizes-in-iis-affects-asp-net.aspx

intenta habilitar aplicaciones de 32 bits en el grupo de aplicaciones de ASP.NET.

+0

¿Por qué el verbo hace alguna diferencia? – Gaz

+0

@Gaz no es así. El código __might__ se ejecutará en cualquier momento dado antes del primer uso. Por lo tanto, podría haberse ejecutado antes de que se reciba la primera solicitud que utiliza el código. –

Cuestiones relacionadas