8

我正在执行以下操作:

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 - 再次没有帮助

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

4

2 回答 2

7

这是因为实际上,您的内联列表初始化对于堆栈来说太大了 - 请参阅msdn 论坛上的这个非常 相关的问题,其中场景几乎相同。

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

例如,考虑以下代码:

    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(需要对象实例来生成属性集Bar)。 这也说明了内联初始化只是一些 C# 语法糖。

在您的情况下,正是这些局部变量导致堆栈崩溃(其中至少有几千个仅用于顶级对象 - 但您也有嵌套的初始化程序)。

C# 编译器也可以将每个所需的引用数量预加载到堆栈中(为每个属性分配弹出每个引用),但这会滥用堆栈,使用局部变量会执行得更好。

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

现在,这项检查还为我们提供了一条围绕这种使用本地人的潜在途径来实现非常庞大的方法:使用迭代器:

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

现在,ILGetInts()现在只是.maxstack 8处于领先地位,没有本地人。查看GetIntsHelper()我们拥有的迭代器函数:

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

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

但是...

查看SomeExample/'<GetIntsHelper>d__5'编译器自动生成的 class ——我们看到 locals 仍然存在——它们刚刚被提升为该类的字段:

.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方法来修复你的代码。yield

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

更好的是 - 将其保存在数据库中:)

于 2012-05-24T11:52:23.507 回答
0

你如何在你的控制器中访问 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 位应用程序。

于 2012-05-24T11:41:50.073 回答