我已经在这里阅读了其他几篇关于此的帖子,但我的问题似乎是面对这里发布的一般智慧(Is the order of static class initialization in C# deterministic?)和几个相关的帖子。
我有一个ASP.Net 4.0应用程序,我的Application_Start()方法通过触摸很多东西来设置它们。其中一件事是为我们的DAL初始化我们的数据库连接字符串,其他人正在初始化其他变量。几天前,我们的发布版本开始爆炸,并且在添加了大量调试日志之后,原因结果是在正式进入Application_Start()之前调用了一个新的静态启动器 并尝试在初始化之前访问我们的DAL。这似乎与我在其他帖子中看到的关于静态构造函数和初始化器的执行顺序的内容相冲突。
具体来说,静态初始值设定项不在代码中被调用的位置运行;发布版本中的堆栈跟踪表明发布编译将它们混合为输入 Application_Startup()的一部分。换句话说,堆栈跟踪在它爆炸时显示堆栈上的Application_Startup() - 但没有与之关联的行号。 DAL初始化的堆栈跟踪显示行号。
为了充实一些伪代码,我们有
Application_Start()
{
DAL.Inst.GetSetting("ConnectionString"); // tickles the singleton to get the DAL initialized.
// numerous other lines of code
if (!AppUtil.IsAdminAccountSetup)
{ // Do admin account setup
}
// More code
}
public class DAL
{
private static DAL _singleton;
static DAL()
{
_singleton = new DAL();
_singleton.Initialize();
}
public static DAL Inst
{
get { return _singleton; }
}
// all the other methods
}
public class AppUtil
{
private static bool _isAdminSetup = InitializeAdminSetup();
public static bool IsAdminAccountSetup
{
get { return _isAdminSetup; }
}
private static bool InitializeAdminSetup()
{
// Call DAL for query
}
private static bool _isProductRegistered = InitializeProdReg();
public static bool IsProductRegistered
{
get { return _isProductRegistered; }
}
private static bool InitializeProdReg()
{
// Call DAL for query
}
}
AppUtil最近添加了IsProductRegistered布尔值,这似乎是事情朝南的时候。
在Application_Start()中没有调用AppUtil.IsProductRegistered,但是当我们查看关于DAL查询顺序的堆栈跟踪时,我们看到AppUtil.IsProductRegistered的初始化程序调用DAL(在触及DAL.Inst之前)和来自 Application_Start()(无行号)。
然后我们看到连接字符串被初始化为DAL静态构造函数的一部分,并且追溯到Application_Start()中的DAL.Inst引用 - 这里 显示行号。
如果我们将AppUtil中的两个静态初始化器更改为单个静态构造函数,那么事情就会以参考顺序重新初始化,并且爆炸就会消失。
但是,面对我所读到的关于.Net表示会做什么的整个情况。
很抱歉信息barf,但我希望有足够的细节来解决难题。
答案 0 :(得分:3)
具有静态构造函数的类和不使用静态构造函数的初始值设定项之间存在很大差异。在没有静态构造函数的类上,它们可以在第一次访问之前的任何时间运行,如果运行时如此决定则更早。使用静态构造函数,它们只能在第一次访问之前立即运行。
DAL
有一个静态构造函数,所以它保证在第一次访问时初始化,而不是更早。
AppUtil
没有静态构造函数,因此它可以尽早运行,特别是它可以在您初始化DAL
之前运行。
如果向AppUtil
添加静态构造函数,即使它是空的,您的直接问题也会消失。
你应该阅读Jon Skeet的C# and beforefieldinit
,他在那里详细解释了这种差异。
引用规范:
如果类中存在静态构造函数(第10.12节),则在执行该静态构造函数之前立即执行静态字段初始值设定项。否则,静态字段初始值设定项在首次使用该类的静态字段之前的实现相关时间执行。
封闭类类型的静态构造函数在给定的应用程序域中最多执行一次。静态构造函数的执行由应用程序域中发生的以下第一个事件触发:
- 创建了类类型的实例。
- 引用类类型的任何静态成员。
在许多层面上,这种初始化模式仍然是一个坏主意。这些初始化程序很难调试,可以随心所欲地改变它们的顺序。
首先,您不应该将静态字段用于此类状态。在初始化代码期间创建这些类的单个实例,而不是使用经典的单例,该单例使用其自己的类外部的状态进行初始化。或者甚至更好,使用适当的依赖注入。这些服务是DI和IoC闪耀的地方。
答案 1 :(得分:0)
除了Loki的评论。 如果依赖于DAL中的静态,你可能会非常糟糕。例如实体框架。 如果不了解.Net ASP管道以及如何使用线程,请不要尝试这样做。 您必须考虑应用程序池中发生的事情。应用程序池的生命周期。线程安全。应用程序池将传入的调用分配给线程。所以你必须使静态线程安全。 您可能需要一个自定义初始化程序,可以在APP启动时为每个请求调用 正如您发现应用程序启动可能已经在之前的请求上运行。
供您考虑:
//Global asax handlers
public override void Init() {
base.Init();
// handlers managed by ASP.Net during Forms authentication
BeginRequest += new EventHandler(BeginRequestHandler);
// PostAuthorizeRequest += new EventHandler(PostAuthHandler);
EndRequest += new EventHandler(EndRequestHandler);
}
考虑 ASP.NET控制器构造函数,用于“更新”上次调用时遗留的上下文。
考虑如何保护静电。 //线程安全????
private static Object _bgalock = new Object();
[ThreadStatic] // thread based static to avoid disasters....
private static sometypeStaticUsedGlobally _ouch;
// Then Get and Set static with lock