c# - 如何在静态变量完成之前获得通知

标签 c# singleton dispose lazy-evaluation

我什么时候可以清除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");

现在,mySqm类实现了一个方法,当对象不再需要时必须调用该方法,并且需要清理它自己(将状态保存到文件系统,释放锁等)。必须在垃圾收集器运行之前(即在调用对象的终结器之前)调用此方法:
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);

再也不用想了。
然后绝望开始了
我甚至绝望到想把我的对象隐藏在一个comIUnknown接口后面,这个接口是引用计数的
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/

相关文章:

java - jboss-eap-quickstarts cluster-ha-singleton 客户端 JNDI 查找类转换异常

c# - using 子句会关闭此流吗?

c# - 必须使用什么命名空间来获取 DataGridComboBoxColumn?

c# - 敌人出现在玩家身后的机会 1 对 5

c# - 如何在 C# 中创建一个只能有一个实例的类

c# - 数据表内部使用?

.net - 我可以在其回调中放置Threading.Timer吗?

c# - 在 VS2008 中的 Windows x64 上调试 x86 .NET 应用程序

c# - 如何将数据写入yaml文件

java - 为什么 private static field = new Singleton 在 Java 中不懒惰?