何时可以在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个,它也无法帮助我解决问题(何时&#34;清理&#34; 静态成员)。
示例用法
Foo.Default.TimerStart("SaveQuestion");
//...snip...
Foo.Default.TimerStop("SaveQuestion");
现在,我的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>
班似乎没有在{1}}懒惰地拥有Dispose
。所以我无法挂钩Value
模式 - 并将其用作调用IDisposable
的时间。我需要自己致电Shutdown
。
但什么时候?
它是Shutdown
变量,它在应用程序/域/ appdomain / apartment的生命周期中存在一次。
有些人确实理解,而有些人并非在static
期间尝试上传我的数据 错误 。
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()
对于实际阅读问题的人,您可能会注意到我并没有真正改变任何事情。我唯一做的就是将方法的名称从 Shutdown 更改为 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;
}
}
}
?
从我的终结器调用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
我已经完整了。我有一个对象,我需要在应用程序域关闭之前&#34;清理&#34; 。当我的对象内部有东西可以调用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
}
}
时需要通知。
没有
我将现有概念从另一种语言迁移到C#中。如果开发人员碰巧使用全局单例实例:
Cleanup
然后Foo.Sqm.TimerStart();
类被延迟初始化。在(本机)应用程序中,保持对对象的引用。在(本机)应用程序关闭期间,保存接口指针的变量设置为Sqm
,并且调用单例对象null
,它可以清理自己。
任何人都不应该打电话。不是destructor
,不是Cleanup
,不是Shutdown
。关闭应该由基础设施自动发生。
的C#相当于什么?我看到自己离开,自我清理?
如果你让垃圾收集器收集对象这一事实很复杂:它已经太晚了。我想要保留的内部状态对象可能已经完成。
如果我可以保证我的课程是从ASP.net使用的,我可以要求Dispose
在域名关闭之前通过注册我的对象来通知:
HostingEnvironment
并实施System.Web.Hosting.HostingEnvironment.RegisterObject(this);
方法:
Stop
除了我的班级不知道我是从 ASP.net ,还是从 WinForms ,或从 WPF < / strong>,或控制台应用程序或shell扩展。
修改:人们似乎对what the IDisposable
pattern exists for感到困惑。删除了对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
}
}
的引用,以消除混淆。
编辑2 :在他们回答问题之前,人们似乎要求完整,详细的示例代码。我个人认为这个问题已经包含了太多的示例代码,因为它不能帮助提出问题。
现在我已经添加了很多代码 sooo ,但这个问题已经丢失了。在问题合理之前,人们拒绝回答问题。现在它已被证明是合理的,没有人会读它。
它就像Dispose
类。人们在他们想要的时候打电话给他们:
System.Diagnostics.Trace
并且永远不必再考虑它了。
我甚至非常绝望,我考虑将我的对象隐藏在COM Trace.WriteLine("Column sort: {0} ms", sortTimeInMs);
接口后面,这是引用计数
IUnknown
然后希望我能欺骗 CLR来减少我界面上的引用计数。当我的引用计数变为零时,我知道一切都在关闭。
缺点是我无法使其发挥作用。
答案 0 :(得分:12)
这里有两个问题:
您坚持认为List<string>
可能已经完成。 List<string>
没有终结器, (这些是不同的操作。)您的SQL
终结器仍会看到有效数据。实际上,终结器可能可以 - 但是当终结器运行时,你需要的某些其他资源可能已经消失了 - 终结器甚至可能不会被终结调用。所以我认为这同时比你预期的更可行 - 而且总的来说更糟糕。
您坚持认为,您不希望通过将其置于开发人员的控制之下来确定这一点,无论是否使用IDisposable
。这只是与.NET提供的对抗。垃圾收集器用于内存资源;任何需要确定性清理(包括刷新等)的非内存资源应该明确清理。您可以使用终结器作为最后的“尽力而为”清理,但不应该以您尝试使用它的方式使用它。
您可以 使用某些方法来尝试解决此问题,例如使用“canary”对象并引用“real”对象:保持对对象的强引用你在其他地方感兴趣,并且在金丝雀对象中有一个终结器只是,因此要完成的唯一的事物是金丝雀对象 - 然后触发适当的刷新和删除最后一个强引用,使真实对象符合GC的条件 - 但它仍然是一个坏主意,并且混合中的静态变量会变得更糟。
同样,你可以使用AppDomain.DomainUnload
事件 - 但是,我不会。当域被卸载时,我会担心其余对象的状态 - 并且不会调用默认域。
基本上,我认为你应该改变你的设计。我们并不真正了解您尝试设计的API的背景,但您现在的方式将无法正常工作。我会尽量避免使用静态变量 - 至少对于任何在时间方面很重要的事情都是如此。幕后可能还有一个对象用于协调,但是在你的API中暴露出来对我来说感觉就像是一个错误。无论你如何抗议其他语言和其他平台,如果你在.NET中工作,你需要接受它就是它的本质。从长远来看,与系统作斗争并不会对你有所帮助。
越早得出结论,您需要更改API设计,就必须考虑新API应该有多长时间。
答案 1 :(得分:1)
AppDomain上有一个ProcessExit
事件你可以尝试挂钩,虽然我不太了解它,它的默认时间限制为2秒。
这样的东西(如果它适合你);
class SQM
{
static Lazy<SQM> _Instance = new Lazy<SQM>( CreateInstance );
private static SQM CreateInstance()
{
AppDomain.CurrentDomain.ProcessExit += new EventHandler( Cleanup );
return new SQM();
}
private static Cleanup()
{
...
}
}
答案 2 :(得分:0)
您不需要致电Dispose
。如果实现IDisposable
的类仅使用托管资源,那么这些资源将在程序完成时自然发布。如果该类使用未受管理的资源,则该类应扩展CriticalFinalizerObject
并在其终结器(以及其Dispose
方法)中释放这些资源。
换句话说,正确使用IDisposable
接口不需要调用Dispose
。可以调用它来在程序中的某个特定点释放托管或非托管资源,但是由于不调用它而发生的泄漏应被视为错误。
修改强>
的C#相当于什么?我看到自己离开,自我清理?
在回答您编辑的问题时,我认为您正在寻找终结者:
class Foo {
~Foo() {
// Finalizer code. Called when garbage collected, maybe...
}
}
但请注意,不能保证会调用此方法。如果你绝对需要它,你应该扩展System.Runtime.ConstrainedExecution.CriticalFinalizerObject
。
答案 3 :(得分:0)
除了Ken的回答,“如何处置我的物体?”的答案。是的,你不能。
您正在寻找的概念是静态解构函数,或者在释放静态方法时运行的解构函数。 This does not exist in managed code,并且在大多数(所有?)案例中都不是必需的。当可执行文件结束时,您很可能会看到正在卸载的静态方法,操作系统将在那时清理所有内容。
如果您绝对需要释放资源,并且必须在所有活动实例之间共享此对象,则可以在确定所有引用都已释放时创建引用计数器并处置该对象。我会首先考虑这是否是正确的方法。新实例需要验证您的对象是否为null
,如果是,则再次实例化。
答案 4 :(得分:0)
AppDomain Domain Unload Event似乎非常适合您所寻找的内容。由于静态变量一直存在,直到AppDomain被卸载,这应该在变量被销毁之前给你一个钩子。
答案 5 :(得分:0)
问题在于,没有一种方法可以知道域何时关闭。你能做的最好的事情就是尝试捕捉各种各样的事件,覆盖你100%(四舍五入到最接近的百分比)的时间。
如果代码在某个域中默认值,则使用DomainUnload
事件。不幸的是,默认 AppDomain不会引发DomainUnload
事件。那么我们就抓住了ProcessExit
:
class InternalSqm
{
//constructor
public InternalSqm ()
{
//...
//Catch domain shutdown (Hack: frantically look for things we can catch)
if (AppDomain.CurrentDomain.IsDefaultAppDomain())
AppDomain.CurrentDomain.ProcessExit += MyTerminationHandler;
else
AppDomain.CurrentDomain.DomainUnload += MyTerminationHandler;
}
private void MyTerminationHandler(object sender, EventArgs e)
{
//The domain is dying. Serialize out our values
this.Dispose();
}
...
}
这已经在“网站”和“WinForms”应用程序中进行了测试。
代码越完整,显示IDisposable
的实现:
class InternalSqm : IDisposable
{
private Boolean _disposed = false;
//constructor
public InternalSqm()
{
//...
//Catch domain shutdown (Hack: frantically look for things we can catch)
if (AppDomain.CurrentDomain.IsDefaultAppDomain())
AppDomain.CurrentDomain.ProcessExit += MyTerminationHandler;
else
AppDomain.CurrentDomain.DomainUnload += MyTerminationHandler;
}
private void MyTerminationHandler(object sender, EventArgs e)
{
//The domain is dying. Serialize out our values
this.Dispose();
}
/// <summary>
/// Finalizer (Finalizer uses the C++ destructor syntax)
/// </summary>
~InternalSqm()
{
Dispose(false); //False: it's not safe to access managed members
}
public void Dispose()
{
this.Dispose(true); //True; it is safe to access managed members
GC.SuppressFinalize(this); //Garbage collector doesn't need to bother to call finalize later
}
protected virtual void Dispose(Boolean safeToAccessManagedResources)
{
if (_disposed)
return; //be resilient to double calls to Dispose
try
{
if (safeToAccessManagedResources)
{
// Free other state (managed objects).
this.CloseSession(); //save internal stuff to persistent storage
}
// Free your own state (unmanaged objects).
// Set large fields to null. Etc.
}
finally
{
_disposed = true;
}
}
}
从进行图像处理的库:
public static class GraphicsLibrary
{
public Image RotateImage(Image image, Double angleInDegrees)
{
Sqm.TimerStart("GraphicaLibrary.RotateImage");
...
Sqm.TimerStop("GraphicaLibrary.RotateImage");
}
}
来自可以执行查询的辅助类
public static class DataHelper
{
public IDataReader ExecuteQuery(IDbConnection conn, String sql)
{
Sqm.TimerStart("DataHelper_ExecuteQuery");
...
Sqm.TimerStop("DataHelper_ExecuteQuery");
}
}
对于WinForms主题绘图
public static class ThemeLib
{
public void DrawButton(Graphics g, Rectangle r, String text)
{
Sqm.AddToAverage("ThemeLib/DrawButton/TextLength", text.Length);
}
}
在网站上:
private void GetUser(HttpSessionState session)
{
LoginUser user = (LoginUser)session["currentUser"];
if (user != null)
Sqm.Increment("GetUser_UserAlreadyFoundInSession", 1);
...
}
在扩展方法
中/// <summary>
/// Convert the guid to a quoted string
/// </summary>
/// <param name="source">A Guid to convert to a quoted string</param>
/// <returns></returns>
public static string ToQuotedStr(this Guid source)
{
String s = "'" + source.ToString("B") + "'"; //B=braces format "{6CC82DE0-F45D-4ED1-8FAB-5C23DE0FF64C}"
//Record how often we dealt with each type of UUID
Sqm.Increment("String.ToQuotedStr_UUIDType_"+s[16], 1);
return s;
}
注意:任何代码都会发布到公共域中。无需归属。
答案 6 :(得分:0)
你花了这么多时间来对抗语言,为什么不重新设计以便问题不存在?
e.g。如果你需要保存变量的状态,而不是在它被销毁之前尝试捕获它,每次修改它时保存它,并覆盖以前的状态。
答案 7 :(得分:-1)
它们将持续AppDomain的持续时间。对方法可以看到对静态变量所做的更改。
MSDN:
如果使用Static关键字声明局部变量,则其生命周期长于声明它的过程的执行时间。如果过程在模块内部,只要应用程序继续运行,静态变量就会存活。