StackOverflowException初始化大型列表

时间:2012-05-24 11:24:20

标签: c# .net

我正在做以下事情:

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

当我从MVC网站内部访问DataHelper.Sellers时,我得到一个StackOverflowException。当我使用Visual Studio进行调试时,堆栈只有六个帧,并且没有通常明显的堆栈溢出迹象(即没有重复的方法调用)。

应用程序调用可以像这样简单地引发异常:

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

额外信息:

  • 当我在单元测试中运行相同的代码时很好
  • 如果我删除了一半卖家(上半部分或下半部分),那么在网络应用程序中就可以了,所以它不是任何具体的问题 卖方
  • 我尝试在第一次通话时将卖家更改为房产并初始化列表 - 没有帮助
  • 我也尝试过将一半列入一个列表,然后将一半添加到另一个列表并将两者合并 - 再次没有帮助

我对这个问题的正确答案印象深刻!

2 个答案:

答案 0 :(得分:7)

这是因为,实际上,您的内联列表初始化对于堆栈来说太大了 - 请参阅此非常 related question over on the msdn forums,其中场景几乎相同。

.Net中的方法具有堆栈深度和大小。 StackOverflowException不仅是由堆栈上的调用次数引起的,而是由每个方法在堆栈中分配内存的总大小引起的。在这种情况下,您的方法太大了 - 这是由局部变量的数量引起的。

举例来说,请考虑以下代码:

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

现在看一下编译时该方法的引入IL(这也是一个发布版本):

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

注意 - /之前的实际位,即SomeExample将取决于定义方法和嵌套类的命名空间和类 - 我必须编辑一些从我正在编写的一些正在进行的代码中删除类型名称的时间!

为什么所有当地人?由于执行内联初始化的方式。每个对象都是新建的并存储在“隐藏”的本地中,这是必需的,以便可以对每个Foo的内联初始化执行属性赋值(需要对象实例来生成{{1的属性集) }})。 这也说明了内联初始化只是一些C#语法糖。

在你的情况下,正是这些本地人导致堆栈爆炸(至少有几千个仅用于顶级对象 - 但你也有嵌套的初始化器。)

C#编译器可以或者预先加载每个所需的引用数量到堆栈(每个属性赋值弹出每个属性)但是那时滥用堆栈的地方使用当地人的表现会好得多。

它也可以使用单个本地 ,因为每个只是写入,然后通过数组索引存储在列表中,不再需要本地。这可能是C#团队需要考虑的问题 - 如果他遇到这个问题,Eric Lippert可能对此有一些想法。

现在,这次考试还为我们提供了一个潜在的途径,围绕你的 非常庞大的方法使用本地人 :使用迭代器:

Bar

现在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 }; } 的IL现在只有GetInts(),而且没有本地人。查看迭代器函数.maxstack 8,我们有:

GetIntsHelper()

所以现在我们已经停止在这些方法中使用所有这些本地人......

<强>但是 ...

查看由编译器自动生成的类.maxstack 2 .locals init ( [0] class SomeExample/'<GetIntsHelper>d__5' ) - 我们看到本地人仍在那里 - 他们刚刚被提升到该类的字段:

SomeExample/'<GetIntsHelper>d__5'

所以问题是 - 如果应用于您的场景,该对象的创建是否也会破坏堆栈?可能不是,因为在内存中它应该像尝试初始化大型数组 - 其中百万元素数组是完全可以接受的(假设在实践中有足够的内存)。

所以 - 可能能够通过转换为使用.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' 每个元素的IEnumerable方法来简单地修复代码。

但最佳实践表明,如果您必须对此静态定义 - 请考虑将数据添加到磁盘上的嵌入式资源或文件(XML和Linq to XML可能是一个不错的选择),然后根据需要从那里加载它

更好 - 将其粘贴在数据库中:)

答案 1 :(得分:0)

如何访问Controller中的DataHelper.Sellers - 您是否在此控制器上使用GEt或POST?对于如此大量的数据,您应该使用POST。

您还需要检查IIS堆栈大小:http://blogs.msdn.com/b/tom/archive/2008/03/31/stack-sizes-in-iis-affects-asp-net.aspx

尝试在ASP.NET的应用程序池中启用32位应用程序。