Asp.net mvc 2 在ASP.NET MVC中,是否在请求之间自动持久化单例?

Asp.net mvc 2 在ASP.NET MVC中,是否在请求之间自动持久化单例?,asp.net-mvc-2,object-lifetime,Asp.net Mvc 2,Object Lifetime,我有一个由数千个整数组成的查找表(LUT),我在相当多的请求中使用它,根据从数据库中获取的内容计算内容 如果我只是创建一个标准的单例来保存LUT,它是在请求之间自动持久化的,还是我特别需要将其推送到应用程序状态 如果它们被自动持久化,那么将它们与应用程序状态存储有什么区别 正确的单例实现是什么样子的?它不需要延迟初始化,但需要线程安全(每个服务器实例上有数千个理论用户)并具有良好的性能 编辑:Jon Skeet的第四个版本看起来很有希望 我不会依赖于请求之间持久化的静态。[尽管不太可能,但总是有

我有一个由数千个整数组成的查找表(LUT),我在相当多的请求中使用它,根据从数据库中获取的内容计算内容

如果我只是创建一个标准的单例来保存LUT,它是在请求之间自动持久化的,还是我特别需要将其推送到应用程序状态

如果它们被自动持久化,那么将它们与应用程序状态存储有什么区别

正确的单例实现是什么样子的?它不需要延迟初始化,但需要线程安全(每个服务器实例上有数千个理论用户)并具有良好的性能

编辑:Jon Skeet的第四个版本看起来很有希望


我不会依赖于请求之间持久化的静态。[尽管不太可能,但总是有可能在请求之间重置进程。]我建议使用HttpContext的缓存对象来持久化请求之间的共享资源

是的,静态成员持续存在(与持续存在不同-它不会“保存”,它永远不会消失),这将包括单例的实现。您可以免费获得一定程度的惰性初始化,就好像它是在静态赋值或静态构造函数中创建的一样,在首次使用相关类之前不会调用它。默认情况下,该创建会锁定,但所有其他用途都必须是线程安全的。考虑到所涉及的并发程度,那么除非singleton是不可变的(您的查找表在应用程序生命周期内不会改变),否则您必须非常小心如何更新它(一种方法是假单例-在更新时,您创建一个新对象,然后锁定分配它以替换当前值;严格来说,不是单例,尽管它看起来“从外部”是单例)

最大的危险是,任何引入全局状态的东西都是可疑的,尤其是在处理像web这样的无状态协议时。尽管如此,它还是可以很好地使用,尤其是作为永久或接近永久数据的内存缓存,特别是当它涉及无法从数据库快速获取的对象图时

不过,陷阱是相当大的,所以要小心。尤其是锁定问题的风险不能低估

编辑,以匹配问题中的编辑:

我最关心的是数组是如何初始化的。很明显,这个例子是不完整的,因为它的每个项都只有0。如果它在初始化时被设置为只读,那么就可以了。如果它是可变的,那么就要非常非常小心线程


还要注意过多这样的查找对扩展的负面影响。虽然您在进行预计算时为大多数TS请求节省了时间,但其影响是在更新单例时会有一段非常繁重的工作。长时间的启动可能是可以忍受的(因为不会经常这样),但随后发生的任意减速可能很难追踪其来源。

编辑:请参阅Jon关于只读锁定的评论

我已经有一段时间没有处理singleton了(我更喜欢让我的IOC容器处理生命周期),但下面是如何处理线程安全问题。你需要锁定任何改变singleton状态的操作。只读操作,如
Compute(int)
不需要锁定

// I typically create one lock per collection, but you really need one per set of atomic operations; if you ever modify two collections together, use one lock.
private object lutLock = new object();
private int[] lut = new int[5000];

public int Compute(Product p) {
    return lut[p.Goo];
}

public void SetValue(int index, int value)
{
    //lock as little code as possible. since this step is read only we don't lock it.
    if(index < 0 || index > lut.Length)
    {
        throw new ArgumentException("Index not in range", "index");
    }
    // going to mutate state so we need a lock now
    lock(lutLock)
    {
        lut[index] = value;
    }
}
//我通常为每个集合创建一个锁,但实际上每个原子操作集都需要一个锁;如果要同时修改两个集合,请使用一个锁。
私有对象lutLock=新对象();
私有整数[]lut=新整数[5000];
公共整数计算(产品p){
返回lut[p.Goo];
}
公共void SetValue(int索引,int值)
{
//锁定尽可能少的代码。因为这个步骤是只读的,所以我们不锁定它。
如果(索引<0 | |索引>查找表长度)
{
抛出新的ArgumentException(“索引不在范围内”、“索引”);
}
//将要改变状态,所以我们现在需要一个锁
锁(lutLock)
{
lut[索引]=值;
}
}

HttpContext的缓存只在应用程序域也处于活动状态时才持续,因此在这方面没有什么好处。事实上,缓存的设计考虑的是过期而不是永久性,因此它实际上没有达到相同的目的(尽管存在重叠情况,其中任何一种都有其价值)。不一定。在这种情况下,这不太可能是一个问题,但如果Compute需要来自lut的多个值,那么它很容易使用更改前后的值的组合。这可能是好的,也可能是坏的,取决于应用程序。ReaderWriterLockSlim在这里可能是合适的。此外,如果SetValue实际上是一个更新方法设置了许多值,最好创建一个新的值,然后锁定分配给lut。Jon:为什么只读操作不需要锁定?多个请求不能同时读取同一个内存导致问题吗?@randomguy,如果还有写操作,只读操作确实需要锁定,这就是为什么我禁用它在这里与Ryan进行了讨论,虽然有时您可以通过读取小于机器大小的引用或内置项的单值来解决问题,但前提是由于缺少内存屏障而过时不是问题(线程缓存内存,因此如果线程B在线程a写入后读取,则可能会获取以前的值,这可能是关键值或标志,具体取决于用途)。如果设置后的唯一操作是读取,则不需要锁定。读取不会影响读取,但会受到写入的影响。也就是说,如果您的唯一风险是内存缓存过时,则可以使用volatile而不是锁定该大小的对象。但是,如果读取操作一次读取多个原子值,则volatile是无效的你有时也可以用一种更为自由的方式来对待那些只会增加、永远不会改变的文字,但知道自己能否做到这一点很快就会变得复杂起来
// I typically create one lock per collection, but you really need one per set of atomic operations; if you ever modify two collections together, use one lock.
private object lutLock = new object();
private int[] lut = new int[5000];

public int Compute(Product p) {
    return lut[p.Goo];
}

public void SetValue(int index, int value)
{
    //lock as little code as possible. since this step is read only we don't lock it.
    if(index < 0 || index > lut.Length)
    {
        throw new ArgumentException("Index not in range", "index");
    }
    // going to mutate state so we need a lock now
    lock(lutLock)
    {
        lut[index] = value;
    }
}