2012-05-24 9 views
8

robię co następuje:StackOverflowException na inicjowanie dużą listę

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 
    }; 
} 

Kiedy dostęp DataHelper.Sellers od wewnątrz mojej stronie MVC, mam StackOverflowException. Kiedy debuguję z Visual Studio, stos ma tylko pół tuzina ramek i nie ma zwykłego oczywistego znaku przepełnienia stosu (to znaczy nie powtarzających się wywołań metod).

Aplikacja połączenie może być tak proste, jak to sprowokować wyjątek:

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

Dodatkowe info:

  • kiedy uruchomić ten sam kod z wewnątrz badanej jednostki jest w porządku
  • jeśli usunę połowę sprzedawców (górna połowa lub dolna połowa), w aplikacji internetowej wszystko jest w porządku, więc nie stanowi to problemu z konkretnym sprzedawcą
  • I h ave próbował zmieniających sprzedawców nieruchomości i inicjalizacji listy na pierwszej rozmowy - bez pomocy
  • Próbowałem też dodać pół do jednej listy potem pół do drugiej i łącząc 2 - znowu, bez pomocy

będę pod wrażeniem przez poprawną odpowiedź na tę!

+1

W jaki sposób uzyskujesz dostęp do listy? – Oded

+6

JEEEEZ, naprawdę powinieneś rozważyć przechowywanie ich gdzie indziej, twarde kodowanie ponad 3500 przedmiotów! : | – mattytommo

+0

@Oded - coś jak sprzedawcy var = DataHelper.Sellers; – Gaz

Odpowiedz

7

Jest to spowodowane faktem, że skutecznie, Twoja lista inline inicjalizacji jest zbyt duże w stosunku do stosu - zobacz bardzorelated question over on the msdn forums gdzie scenariusz jest prawie na identyczne.

Metoda z .NET ma zarówno głębokość stosu, jak i rozmiar. A StackOverflowException jest spowodowane nie tylko liczbą wywołań na stosie, ale także ogólnym rozmiarem przydziału pamięci dla każdej metody w stosie. W tym przypadku twoja metoda jest po prostu zbyt duża - i jest to spowodowane liczbą zmiennych lokalnych.

Tytułem przykładu, rozważmy następujący kod:

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 } }; 
    } 

Spójrzmy teraz na lead-in IL tej metody, gdy skompilowany (jest to build release, zbyt):

.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 
) 

Uwaga - rzeczywisty bit przed numerem /, tj. SomeExample będzie zależał od przestrzeni nazw i klasy, w której zdefiniowana jest metoda i klasa zagnieżdżona - musiałem edytować kilka razy, aby usunąć nazwy typów z niektórych kodów postępu, które 'piszę w pracy!

Dlaczego wszyscy mieszkańcy? Ze względu na sposób inicjalizacji inline.Każdy obiekt jest nowszy i przechowywany w "ukrytym" lokalnym, jest to wymagane, aby można było przypisać właściwości w inicja- tywach początkowych każdego z komputerów Foo (instancja obiektu jest wymagana do wygenerowania zestawu właściwości dla Bar). Pokazuje to również, że inicjalizacja inline to tylko cukier syntaktyczny C#.

W twoim przypadku to ci miejscowi powodują obrażenia stosu (jest ich co najmniej kilka tysięcy dla obiektów najwyższego poziomu - ale masz również zagnieżdżone inicjatory).

C# kompilator mógł alternatywnie wstępnie załadować liczbę referencji wymaganych dla każdego na do stosu (popping każde jednorazowe dla każdego zadania własności), ale potem, że nadużywa stos, gdzie korzystanie z mieszkańcami będzie wykonywał dużo lepszy.

To może również korzystać z jednego lokalnego, ponieważ każdy jest tylko napisane zbyt, a następnie przechowywane w wykazie indeksu tablicy, lokalny nie jest jeszcze konieczna. To może być dla zespołu C# do rozważenia - Eric Lippert może mieć kilka przemyśleń na ten temat, jeśli natknie się na ten wątek.

Teraz badanie to daje nam również potencjalne trasy wokół tego wykorzystaniu miejscowych dla bardzo masywnej metody: Użyj iterator:

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 }; 
} 

Teraz IL dla GetInts() teraz po prostu ma .maxstack 8 na głowa, a nie miejscowi. Patrząc na iterator GetIntsHelper() mamy:

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

Więc teraz my przestaliśmy używać tych wszystkich mieszkańców w tych metod ...

Ale ...

Spójrz na klasy SomeExample/'<GetIntsHelper>d__5', który został automatycznie wygenerowany przez kompilator - i widzimy, że miejscowi wciąż tam są - właśnie zostali awansowani na pola w tej klasie:

.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' 

Pytanie brzmi: czy utworzenie tego obiektu spowoduje również strącenie stosu, jeśli zostanie zastosowane do scenariusza? Prawdopodobnie nie, ponieważ w pamięci powinno to być jak inicjowanie dużej tablicy - gdzie tablice z milionami elementów są całkiem dopuszczalne (zakładając w praktyce wystarczającą pamięć).

Dzięki temu można być w stanie naprawić swój kod, po prostu przechodząc do korzystania z metody IEnumerable, że każdy element jest yield.

Ale najlepsza praktyka mówi, że jeśli absolutnie musisz mieć to statycznie zdefiniowane - rozważ dodanie danych do osadzonego zasobu lub pliku na dysku (XML i Linq do XML może być dobrym wyborem), a następnie załadowanie go z tego miejsca żądanie.

Lepiej - przyklej w bazie danych :)

+0

Dzięki Andras. Ten link, który publikujesz, nie wyjaśnia w zasadzie * dlaczego * - więc sądzę, że odpowiedź brzmi, że nikt oprócz zespołu CLR naprawdę * dokładnie * wie, ale dobrze jest znaleźć kogoś, kto miał ten sam problem. Nie mam wymogu, aby to zostać osadzone, to tylko prosty sposób na radzenie sobie z niektórymi danymi testowymi podczas prototypowania. Natychmiast przejdzie do zewnętrznego źródła. – Gaz

+0

@Gaz zobaczyć moją zaktualizowaną odpowiedź - to powinno wyjaśnić, dlaczego –

+0

genialny! Dzięki. Sądzę, że kluczowym elementem dodatkowych informacji wyjaśniających, dlaczego mam problem, jest punkt Iwana, że ​​rozmiar stosu ASP.NET wynosi 256K. Tak więc przy 64-bitowych referencjach i 3500 obiektach, które zabierają mi nieco powyżej tego limitu. Co ciekawe, wypróbowałem Twoją sugestię użycia iteratora i tym razem nie mogłem go skompilować, ponieważ powiedział, że nie ma wystarczającej ilości miejsca na dysku, aby utworzyć plik pdb. Jednak mam 2 dyski - jeden z 1,5 GB za darmo i jeden z około 5 GB. To jest jakiś plik pdb! (Edycja: zdaję sobie sprawę, że prawdziwe wyjaśnienie będzie bardziej subtelne ...) – Gaz

0

w jaki sposób uzyskujesz dostęp do modułów DataHelper.Seller w kontrolerze - czy używasz tego kontrolera w trybie GEt lub POST? W przypadku tak dużej ilości danych powinieneś użyć testu POST.

Również trzeba sprawdzić rozmiar stosu IIS: http://blogs.msdn.com/b/tom/archive/2008/03/31/stack-sizes-in-iis-affects-asp-net.aspx

próby włączenia 32-bitowych aplikacji w puli aplikacji w ASP.NET za.

+0

Dlaczego czasownik ma jakąkolwiek różnicę? – Gaz

+0

@Gaz to nie robi. Kod __might__ może być wykonany w dowolnym momencie przed pierwszym użyciem. Mogło więc zostać wykonane, zanim pierwsze żądanie z użyciem kodu zostanie kiedykolwiek odebrane. –

Powiązane problemy