【问题标题】:Singletons and ASP.NET MVC单例和 ASP.NET MVC
【发布时间】:2011-04-26 03:51:20
【问题描述】:

现在我遇到了一个我刚刚编写的用于 ASP.NET MVC 的 Singleton 的问题——我的 Singleton 看起来像这样:

public sealed class RequestGenerator : IRequestGenerator
{
    // Singleton pattern
    private RequestGenerator() 
    {
        requestList = new Stack<Request>();
        appSettings = new WebAppSettings();
    }
    private static volatile RequestGenerator instance = new RequestGenerator();
    private static Stack<Request> requestList = new Stack<Request>();
    // abstraction layer for accessing web.config
    private static IAppSettings appSettings = new WebAppSettings();
    // used for "lock"-ing to prevent race conditions
    private static object syncRoot = new object();
    // public accessor for singleton        
    public static IRequestGenerator Instance
    {
        get
        {
            if (instance == null)
            {
                lock (syncRoot)
                {
                    if (instance == null)
                    {
                        instance = new RequestGenerator();
                    }
                }
            }
            return instance;
        }
    }     
    private const string REQUESTID = "RequestID";

    // Find functions
    private Request FindRequest(string component, string requestId)
    private List<Request> FindAllRequests(string component, string requestId)

    #region Public Methods required by Interface
    // Gets and increments last Request ID from Web.Config, creates new Request, and returns RequestID
    public string GetID(string component, string userId)

    // Changes state of Request to "submitted"
    public void SetID(string component, string requestId)

    // Changes state of Request to "success" or "failure" and records result for later output
    public void CloseID(string component, string requestId, bool success, string result)

    // Verifies that Component has generated a Request of this ID
    public bool VerifyID(string component, string requestId)

    // Verifies that Component has generated a Request of this ID and is owned by specified UserId
    public bool VerifyID(string component, string userId, string requestId)

    // Returns State of Request ID (Open, Submitted, etc.)
    public Status GetState(string component, string requestId)

    // Returns Result String of Success or Failure.
    public string GetResult(string component, string requestId)
    #endregion
}

我的控制器代码如下所示:

public ViewResult SomeAction()
{
    private IRequestGenerator reqGen = RequestGenerator.Instance;
    string requestId = reqGen.GetID(someComponentName, someUserId);
    return View(requestId);
}

我第一次点击控制器时一切正常。 “reqGen”被分配了单例的实例。一个新的 Request 实例被添加到 Singleton 的内部列表中。然后我们返回一个 View()。下次我点击这个控制器的 SomeAction() 时,我希望 Singleton 包含带有我刚刚添加的 SomeClass 实例的 List,但 List 是空的。

发生了什么事?垃圾收集是否吞噬了我的对象?在 ASP.NET MVC 中实现单例模式时有什么特别需要考虑的吗?

谢谢!

编辑:啊,灯泡刚刚亮了。因此,每个新页面请求都发生在一个全新的过程中!知道了。 (我的背景是桌面应用程序开发,所以这对我来说是一个不同的范例......)

EDIT2:当然,这里有更多的说明。我的应用程序需要一个请求编号系统,其中所请求的内容需要一个唯一的 ID,但我没有可用的数据库。但它必须可供每个用户使用以记录每个请求的状态。我还意识到它可以兼作调节会话的一种方式,例如,如果用户双击请求按钮。单例似乎是要走的路,但意识到每个请求都在自己的进程中基本上消除了单例。而且我猜这也消除了静态类,对吧?

EDIT3:好的,我已经添加了我正在使用的实际代码(为了简单起见,减去每个方法的实现......)我希望这更清楚。

EDIT4:我将绿色复选标记授予 Chris,因为我开始意识到应用程序级单例就像拥有一个 Global(并且 global 是邪恶的,对吗?) ——除了开玩笑,最好的选择确实是拥有一个数据库,而 SQLite 似乎是目前最合适的选择,尽管我可以肯定地看到自己将来会迁移到 Oracle 实例。不幸的是,最好的选择是使用 ORM,但这是另一个需要攀登的学习曲线。混蛋。

EDIT5:最后一次编辑,我发誓。 :-)

所以我尝试使用 HttpRuntime.Cache,但惊讶地发现我的缓存不断刷新/失效,无法弄清楚发生了什么。好吧,我被我正在做的其他事情的副作用绊倒了:写到“Web.config”

答案 --> 我不知道,当“web.config”无论如何改变时,应用程序就会重新启动!是的,一切都被扔掉了。我的单身,我的缓存,一切。嘎。难怪没有什么工作正常。看起来写回 web.config 通常是不好的做法,我现在将避免这样做。

再次感谢所有帮助我解决这个难题的人。

【问题讨论】:

  • 如果您这样做是为了缓存原因,那么请考虑使用 HttpContext.Current.Cache。
  • 这是一个很好的问题!观看 Pretzel 完成向 Web 开发过渡的过程将对许多未来的程序员有所帮助
  • @Chris,是的,我尝试在我的问题和答案中详细解释所有内容,以便每个人都可以从我的课程中学习(另外,当我几个月后不可避免地忘记某些课程时,我可以快速提醒自己我学到了什么... ;-)

标签: asp.net-mvc garbage-collection singleton


【解决方案1】:

单例特定于处理实例。正在为每个页面请求生成一个新实例。页面请求通常被认为是无状态的,因此来自一个页面的数据不会仅仅停留在另一个页面上。

为了让它在应用程序级别工作,必须在此处声明实例变量。有关如何创建应用程序级变量的提示,请参阅 this question。请注意,这会使它在所有请求中都可用.. 这并不总是您想要的。

当然,如果您尝试实现某种类型的会话状态,那么您可能只使用会话或使用某种类型的缓存过程。

更新
根据您的编辑:静态类不应维护数据。它的目的是简单地将一些常用方法组合在一起,但它不应该在方法调用之间存储数据。单例是完全不同的东西,因为它是一个你只希望为请求创建一个对象的类。

这些似乎都不是你想要的。

现在,整个应用程序都可以使用应用程序级别的单例,但这会跨越请求并且必须进行相应的编码。

听起来您几乎是在尝试构建内存数据存储。您可以使用各种缓存机制之一,例如 .NET Page.Cache、MemCache 或 Enterprise Library's Caching Application Block

但是,如果托管应用程序的工作进程被回收,所有这些都存在被清除的问题。这可能发生在最坏的时候。并且会根据随机事件发生,例如内存使用情况,某些计时器已过期, 一定数量的页面重新编译等。

相反,我强烈建议使用某种类型的持久存储。无论是您从中读取/写入的 xml 文件还是将 SQL Lite 之类的内容嵌入到应用程序中。 SQL Lite 是一个非常轻量级的数据库,不需要在服务器上安装;你只需要这些程序集。

【讨论】:

  • 那么这是“静态类”与“单例”比较中我应该实现静态类的实例之一吗?
  • @Pretzel:不知道类是什么或者为什么你需要它的一个副本很难说。班级做什么?如果您真的需要它作为数据容器,那么缓存机制或会话可能会更好。虽然我不会在会话中存储课程。如果您提供了您想要完成的任务的详细信息,那么这里的很多人都可以为您提供一些关于如何实现目标的指导。
  • 感谢您的更新。我已经添加了我正在使用的实际代码,因此您可以更清楚地了解我在做什么。是的,我正在构建一个内存数据存储。我不打算将请求存储到 DB 或 XML(嗯,还没有。)请求通过 SMTP 发送给用户。我将看一下 SQL Lite。我应该考虑使用 NHibernate 还是实体框架?还是我应该直接编写 SQL Lite 代码?
  • @Pretzel:我不是 ORM 工具的粉丝,但这些当然是选择。就个人而言,我会直接使用 SQL Lite。
  • 查看我上面的最新编辑。经典的“面子”在这里。但是我怎么知道呢?
【解决方案2】:

您可以使用依赖注入来控制类的生命周期。如果您使用的是 Castle Windsor,则可以在 web.config 中添加以下行。

<component id="MySingleton" service="IMySingleton, MyInterfaceAssembly" 
type="MySingleton, MyImplementationAssembly" lifestyle="Singleton" />

当然,连接应用程序以使用 DI 的话题超出了我的回答范围,但要么您正在使用它并且此答案对您有所帮助,要么您可以在这个概念上达到顶峰并爱上它。 :)

【讨论】:

  • 几个月前我开始阅读 DI,现在已经养成了对接口(而不是具体类)进行编码的习惯,所以在我的代码的某些地方我会“手动注入” ,但我仍然没有设法采用像 Castle Windsor 或 Ninject 这样的 DI Container。 (我还没有完全理解如何使用它......)也许这将是我这个周末的项目。
  • 我尝试使用 Ninject 进行 DI,我能够让它成功注入单例,但是一旦进程终止,单例也随之死亡,所以这对我不起作用。
猜你喜欢
  • 1970-01-01
  • 2011-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多