我什么时候可以清除c中静态变量中存储的对象?
我有一个静态变量是lazily initialized:
public class Sqm
{
private static Lazy<Sqm> _default = new Lazy<Sqm>();
public static Sqm Default { get { return _default.Value; } }
}
注意:我刚刚把
Foo
改成了一个static
类。如果Foo
是静态的或不是静态的,它不会以任何方式改变问题。但是有些人相信,不首先构造Sqm
的实例,就不可能构造Foo
的实例。即使我确实创建了一个Foo
对象;即使我创建了100个,也无助于解决问题(何时“清理”静态成员)。示例用法
Foo.Default.TimerStart("SaveQuestion");
//...snip...
Foo.Default.TimerStop("SaveQuestion");
现在,my
Sqm
类实现了一个方法,当对象不再需要时必须调用该方法,并且需要清理它自己(将状态保存到文件系统,释放锁等)。必须在垃圾收集器运行之前(即在调用对象的终结器之前)调用此方法:public class Sqm
{
var values = new List<String>();
Boolean shutdown = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
public void Shutdown()
{
if (!alreadyShutdown)
{
Cleanup(values);
alreadyShutdown = true;
}
}
}
何时何地,我可以调用我的
Shutdown()
方法?注意:我不希望使用
Sqm
类的开发人员不得不担心调用Shutdown
。那不是他的工作。在其他语言环境中,他不必这么做。Lazy<T>
类似乎没有对其惰性拥有的Dispose
调用Value
。所以我不能挂接IDisposable
模式-并用它作为调用Shutdown
的时间。我需要亲自打电话给Shutdown
。但是什么时候?
它是一个
static
变量,它存在于应用程序/域/ AppDeave/Soad的生命周期中。是的,定稿器的时间不对
有些人明白,有些人不明白,试图在
finalizer
期间上载我的数据是错误的。///WRONG: Don't do this!
~Sqm
{
Shutdown(_values); //<-- BAD! _values might already have been finalized by the GC!
}
为什么错了?因为
values
可能不再存在了。你不能控制哪些对象是按什么顺序完成的。完全有可能values
是在包含Sqm
之前完成的。那处置呢?
IDisposable
接口和Dispose()
方法是一种约定。如果我的对象实现了一个Dispose()
方法,就永远不会被调用。事实上,我可以继续实施它:public class Sqm : IDisposable
{
var values = new List<String>();
Boolean alreadyDiposed = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
public void Dispose()
{
if (!alreadyDiposed)
{
Cleanup(values);
alreadyDiposed = true;
}
}
}
对于真正阅读问题的人,你可能会注意到我实际上没有改变任何东西。我做的唯一一件事是将方法的名称从shutdown改为dispose。dispose模式只是一个约定。我还有一个问题:我什么时候可以打电话给
Dispose
?你应该从终结器中调用dispose
从终结器调用
Dispose
与从终结器调用Shutdown
一样不正确(它们完全错误):public class Sqm : IDisposable
{
var values = new List<String>();
Boolean alreadyDiposed = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
public void Dispose()
{
if (!alreadyDiposed)
{
Cleanup(_values); // <--BUG: _values might already have been finalized by the GC!
alreadyDiposed = true;
}
}
~Sqm
{
Dispose();
}
}
因为,
values
可能不再存在了。为了完整起见,我们可以返回完整的原始正确代码:public class Sqm : IDisposable
{
var values = new List<String>();
Boolean alreadyDiposed = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
protected void Dispose(Boolean itIsSafeToAlsoAccessManagedResources)
{
if (!alreadyDiposed)
{
if (itIsSafeToAlsoAccessManagedResources)
Cleanup(values);
alreadyDiposed = true;
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
~Sqm
{
Dispose(false); //false ==> it is not safe to access values
}
}
我已经走了一大圈了。我有一个在应用程序域关闭之前需要“清理”的对象。当我的对象可以调用
Cleanup
时,需要通知它内部的某些内容。让开发者称之为
不。
我正在把现有的概念从另一种语言迁移到C语言中。如果开发人员碰巧使用全局单例实例:
Foo.Sqm.TimerStart();
然后
Sqm
类被延迟初始化。在(本机)应用程序中,保留对对象的引用。在(本机)应用程序关闭期间,保存接口指针的变量设置为null
,并调用单例对象的destructor
,它可以自行清理。任何人都不应该打电话给任何人。不
Cleanup
,不Shutdown
,不Dispose
。基础设施应自动关闭。什么是C相当于我看到自己离开,清理自己?
如果让垃圾回收器收集对象,事情就复杂了:太晚了。我要持久化的内部状态对象可能已经完成。
如果从asp.net
如果我可以保证我的类是从asp.net使用的,我可以在域关闭之前通过向它注册我的对象来请求
HostingEnvironment
通知:System.Web.Hosting.HostingEnvironment.RegisterObject(this);
并实现
Stop
方法:public class Sqm : IDisposable, IRegisteredObject
{
var values = new List<String>();
Boolean alreadyDiposed = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
protected void Dispose(Boolean itIsSafeToAlsoAccessManagedResources)
{
if (!alreadyDiposed)
{
if (itIsSafeToAlsoAccessManagedResources)
Cleanup(values);
alreadyDiposed = true;
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
Sqm
{
//Register ourself with the ASP.net hosting environment,
//so we can be notified with the application is shutting down
HostingEnvironment.RegisterObject(this); //asp.net will call Stop() when it's time to cleanup
}
~Sqm
{
Dispose(false); //false ==> it is not safe to access values
}
// IRegisteredObject
protected void Stop(Boolean immediate)
{
if (immediate)
{
//i took too long to shut down; the rug is being pulled out from under me.
//i had my chance. Oh well.
return;
}
Cleanup(); //or Dispose(), both good
}
}
但我的类不知道是否将从asp.net、winforms、wpf、控制台应用程序或shell扩展调用我。
编辑:人们似乎被what the
IDisposable
pattern exists for搞糊涂了。删除对Dispose
的引用以消除混淆。编辑2:在回答问题之前,人们似乎需要完整、详细的示例代码。就我个人而言,我认为这个问题已经包含了太多的示例代码,因为它不能帮助您提出问题。
现在我已经添加了很多代码,这个问题已经丢失了。人们拒绝回答一个问题,直到问题得到证明。既然这是正当的,没人会读的。
就像诊断一样
就像是
System.Diagnostics.Trace
类。人们想叫它的时候就叫它:Trace.WriteLine("Column sort: {0} ms", sortTimeInMs);
再也不用想了。
然后绝望开始了
我甚至绝望到想把我的对象隐藏在一个com
IUnknown
接口后面,这个接口是引用计数的public class Sqm : IUnknown
{
IUnknown _default = new Lazy<Sqm>();
}
然后我希望我可以欺骗clr减少我接口上的引用计数。当我的参考计数变为零时,我知道一切都在关闭。
这样做的缺点是我做不到。
最佳答案
这里有两个问题:
您坚持认为List<string>
可能已经完成。List<string>
没有终结器,而且它还不会被垃圾回收(因为您有对它的引用)。(这些是不同的操作。)您的SQL
终结器仍将看到有效的数据。所以,实际上,终结器可能是可以的——尽管当终结器运行您需要的其他一些资源时,终结器可能已经消失了——并且终结器甚至可能不会被调用。所以我认为这同时比你预期的更可行,而且总体上是一个更糟糕的主意。
无论是否使用IDisposable
,您都坚持不希望通过将其置于开发人员的控制之下使其具有确定性。这只是为了对抗.net提供的功能。垃圾收集器用于内存资源;任何需要确定性清理(包括刷新等)的非内存资源都应显式清理。您可以使用终结器作为最后一次“尽力而为”清理,但它不应以您尝试使用它的方式使用。
有一些方法可以用来解决这个问题,比如使用“canary”对象来引用“real”对象:保持对其他地方感兴趣的对象的强引用,并在canary对象中使用finalizer,因此,唯一需要最终确定的是canary对象,它随后会触发适当的刷新并删除最后一个强引用,使真正的对象符合gc的条件,但这从根本上来说仍然是一个坏主意,而且在混合的静态变量中,情况会变得更糟。
同样地,您可以使用AppDomain.DomainUnload
事件-但我不会这样做。在卸载域时,我会担心其他对象的状态-并且不会为默认域调用它。
基本上,我认为你应该改变你的设计。我们不太清楚你试图设计的api的背景,但你目前的方式是行不通的。我会尽量避免静态变量,个人-至少对于任何重要的时间。在幕后仍然可能有一个用于协调的对象,但是在api中公开它对我来说是个错误。不管你对其他语言和其他平台有多少抗议,如果你在.NET中工作,你需要接受它是什么。与体制作斗争从长远来看对你没有帮助。
越早得出需要更改api设计的结论,就越需要考虑新api应该是什么样子。
关于c# - 如何在静态变量完成之前获得通知,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18020861/