2012-05-24 22 views
8

Tôi đang làm như sau:StackOverflowException trên initialising danh sách lớn

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

Khi tôi truy cập DataHelper.Sellers từ bên trong trang web MVC của tôi, tôi nhận được một StackOverflowException. Khi tôi gỡ lỗi với Visual Studio, ngăn xếp chỉ có nửa tá khung và không có dấu hiệu rõ ràng thông thường của tràn ngăn xếp (nghĩa là không có cuộc gọi phương thức lặp lại).

Cuộc gọi ứng dụng có thể đơn giản như thế này để khiêu khích các ngoại lệ:

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

thêm thông tin:

  • khi tôi chạy cùng mã từ bên trong một thử nghiệm đơn vị nó là tốt
  • nếu tôi xóa một nửa số người bán (nửa trên hoặc nửa dưới), điều đó là tốt trong ứng dụng web, do đó, không có vấn đề gì với bất kỳ người bán cụ thể nào
  • I h ave cố gắng thay đổi người bán đến một bất động sản và initialising danh sách theo yêu cầu đầu tiên - không có sự giúp đỡ
  • Tôi cũng đã cố gắng thêm nửa đến một danh sách sau đó nửa khác và kết hợp 2 - một lần nữa không có sự giúp đỡ

tôi sẽ rất ấn tượng bởi câu trả lời đúng cho câu trả lời này!

+1

Bạn đang truy cập danh sách như thế nào? – Oded

+6

JEEEEZ, bạn thực sự nên xem xét việc lưu trữ chúng ở nơi khác, mã hóa cứng hơn 3500 mục! : | – mattytommo

+0

@Oded - giống như var seller = DataHelper.Sellers; – Gaz

Trả lời

7

Điều này là do thực tế, việc khởi tạo danh sách nội tuyến của bạn quá lớn cho ngăn xếp - xem này rấtrelated question over on the msdn forums trong trường hợp kịch bản này giống hệt nhau.

Phương thức trong .Net có cả chiều sâu và kích thước ngăn xếp. Một StackOverflowException được gây ra không chỉ bởi số lượng các cuộc gọi trên ngăn xếp, nhưng kích thước tổng thể của mỗi phương pháp phân bổ bộ nhớ trong ngăn xếp. Trong trường hợp này, phương pháp của bạn chỉ là quá lớn - và đó là do số lượng biến cục bộ.

Bằng một ví dụ, hãy xem xét đoạn mã sau:

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

Bây giờ nhìn vào chì trong IL của phương pháp mà khi biên dịch (đây là một xây dựng phát hành, quá):

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

Lưu ý - bit thực tế trước /, tức là SomeExample sẽ phụ thuộc vào không gian tên và lớp trong đó phương thức và lớp lồng nhau được xác định - Tôi đã phải chỉnh sửa vài lần để loại bỏ các tên tệp khỏi mã tiến trình mà tôi 'tôi đang viết tại nơi làm việc!

Tại sao tất cả những người dân địa phương đó? Bởi vì cách khởi tạo nội tuyến được thực hiện.Mỗi đối tượng được tạo mới và được lưu trữ trong một địa chỉ 'ẩn', điều này là bắt buộc để việc gán thuộc tính có thể được thực hiện trên khởi tạo nội tuyến của mỗi Foo (trường hợp đối tượng được yêu cầu để tạo thuộc tính được đặt cho Bar). Điều này cũng thể hiện cách khởi tạo nội tuyến chỉ là một số đường cú pháp C#.

Trong trường hợp của bạn, đó là những người dân địa phương làm cho ngăn xếp phát nổ (có ít nhất một vài nghìn trong số đó chỉ dành cho các đối tượng cấp cao nhất - nhưng bạn cũng có các trình khởi tạo lồng nhau).

Trình biên dịch C# có thể cách tải trước số lượng tham chiếu cần thiết cho mỗi lần vào ngăn xếp (popping từng phần cho mỗi phân bổ thuộc tính) nhưng sau đó lạm dụng ngăn xếp nơi sử dụng người dân địa phương sẽ thực hiện nhiều tốt hơn.

Nó cũng có thể sử dụng một địa phương đơn, vì mỗi chỉ đơn thuần bằng văn bản-quá và sau đó được lưu trữ trong danh sách bằng chỉ số mảng, các địa phương không bao giờ cần nữa. Đó có thể là một cho đội C# để xem xét - Eric Lippert có thể có một vài suy nghĩ về điều này nếu anh ta tình cờ về chủ đề này.

Bây giờ, kiểm tra này cũng cho chúng ta một con đường tiềm năng xung quanh sử dụng này của người dân địa phương cho phương pháp rất lớn của bạn: sử dụng một 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 }; 
} 

Bây giờ IL cho GetInts() nay chỉ đơn giản là có .maxstack 8 tại đầu, và không có người dân địa phương. Nhìn vào chức năng lặp GetIntsHelper() ta có:

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

Vì vậy, bây giờ chúng tôi đã ngừng sử dụng tất cả những người dân địa phương trong các phương pháp đó ...

Nhưng ...

Nhìn vào lớp SomeExample/'<GetIntsHelper>d__5', đã được trình biên dịch tạo tự động - và chúng tôi thấy rằng người dân địa phương vẫn ở đó - họ vừa được thăng cấp lên các trường trên lớp đó:

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

Vì vậy, câu hỏi đặt ra là - liệu việc tạo đối tượng đó cũng có thổi bay ngăn xếp nếu áp dụng cho kịch bản của bạn không? Có lẽ là không, bởi vì trong bộ nhớ nó nên giống như cố gắng khởi tạo mảng lớn - nơi mà các mảng triệu phần tử là khá chấp nhận được (giả sử đủ bộ nhớ trong thực tế).

Vì vậy - bạn có thể có thể sửa chữa mã của bạn khá đơn giản bằng cách di chuyển sang sử dụng một phương pháp IEnumerable rằng yield s mỗi phần tử. Tuy nhiên, thực hành tốt nhất nói rằng nếu bạn hoàn toàn phải có định nghĩa tĩnh này - hãy xem xét việc thêm dữ liệu vào tài nguyên hoặc tệp được nhúng trên đĩa (XML và LINQ to XML có thể là một lựa chọn tốt) và sau đó tải nó từ đó nhu cầu.

Vẫn còn tốt hơn - dán vào cơ sở dữ liệu :)

+0

Cảm ơn Andras. Liên kết mà bạn đăng không thực sự giải thích * tại sao * - vì vậy tôi đoán câu trả lời là không có ai nhưng nhóm CLR thực sự * biết * chính xác ... vẫn tốt khi tìm người khác có cùng vấn đề. Tôi không yêu cầu điều này để được nhúng nó chỉ là một cách dễ dàng để đối phó với một số dữ liệu thử nghiệm trong khi tạo mẫu. Nó sẽ đi vào một nguồn bên ngoài. – Gaz

+0

@Gaz xem câu trả lời cập nhật của tôi - điều đó sẽ giải thích lý do tại sao –

+0

rực rỡ! Cảm ơn. Tôi đoán bit chính của thông tin bổ sung để giải thích lý do tại sao tôi có vấn đề ở đây là điểm của Ivan rằng kích thước ngăn xếp ASP.NET là 256K. Vì vậy, với tài liệu tham khảo 64 bit và 3500 đối tượng mà đưa tôi vượt quá giới hạn đó. Thật thú vị, tôi đã thử đề xuất của bạn để sử dụng trình lặp và lần này tôi không thể biên dịch được vì nó cho biết không có đủ bộ nhớ để tạo tệp pdb. Tuy nhiên tôi có 2 đĩa - một với 1,5 GB miễn phí và một với khoảng 5GB. Đó là một số tập tin pdb! (Edit: Tôi nhận ra lời giải thích thực sự sẽ tinh tế hơn ...) – Gaz

0

cách bạn truy cập DataHelper.Sellers trong Bộ điều khiển - bạn đang sử dụng GEt hoặc POST cho bộ điều khiển này? Đối với một số lượng lớn dữ liệu bạn nên sử dụng POST.

Ngoài ra, bạn cần phải kiểm tra IIS ngăn xếp kích thước: http://blogs.msdn.com/b/tom/archive/2008/03/31/stack-sizes-in-iis-affects-asp-net.aspx

Cố gắng Enable Applications 32-Bit trong hồ bơi ứng dụng của ASP.NET.

+0

Tại sao động từ làm cho bất kỳ sự khác biệt? – Gaz

+0

@ Rất tiếc. Mã __might__ được thực hiện tại bất kỳ thời điểm nào trước khi sử dụng lần đầu tiên. Vì vậy, nó có thể đã được thực hiện trước khi yêu cầu đầu tiên bằng cách sử dụng mã là bao giờ nhận được. –

Các vấn đề liên quan