How to get notified before static variables are finalized

时光毁灭记忆、已成空白 提交于 2019-11-29 09:55:59

There are two problems here:

  • You are insistent that the List<string> might have been finalized. List<string> doesn't have a finalizer, and it won't have been garbage collected yet (because you have a reference to it). (Those are different operations.) Your SQL finalizer will still see valid data. So actually, a finalizer might be okay - although by the time the finalizer runs some other resources that you require may have gone away - and the finalizer may not even end up being called. So I think this is simultaneously more feasible than you expect - and a worse idea in general.

  • You are insistent that you don't want to make this deterministic by putting it in the control of the developer, whether that's using IDisposable or not. This is simply fighting against what .NET provides. The garbage collector is meant to be for memory resources; any non-memory resources which require deterministic cleanup (including flushing etc) should be cleaned up explicitly. You can use a finalizer as a last "best effort" clean-up, but it should not be used in the way you're trying to use it.

There are some approaches that you could use to try to work around this, such as using a "canary" object with a reference to the "real" object: keep a strong reference to the object you're interested elsewhere, and have a finalizer just in the canary object, so that the only thing to be finalized is the canary object - which then triggers the appropriate flushing and removes the last strong reference, leaving the real object eligible for GC - but it's still fundamentally a bad idea, and with static variables in the mix it becomes even worse.

Likewise you can use the AppDomain.DomainUnload event - but again, I wouldn't. By the time the domain is being unloaded, I would be worried about the state of the rest of the objects - and it doesn't get called for the default domain.

Basically, I think you should change your design. We don't really know the background of the API you're trying to design, but the way you're going at the moment just won't work. I would try to avoid the static variable, personally - at least for anything which is important in terms of timing. There could still be a single object behind the scenes for coordination, but exposing that in your API feels like a mistake to me. However much you protest about other languages and other platforms, if you're working in .NET you need to accept it is what it is. Fighting against the system is not going to help you in the long term.

The earlier you come to the conclusion that you need to change your API design, the more time you have to think about what that new API should look like.

There's a ProcessExit event on the AppDomain you could try to hook, though I don't know much more about it, and it has a default time limit of 2 seconds.

Something like this (if it's suitable for you);

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()
    {
        ...
    }

}
Will Eddins

In addition to Ken's answer, the answer to "How can I dispose my object?" is, you can't.

The concept you're looking for is a static deconstructor, or a deconstructor that would run when the static methods are being freed. This does not exist in managed code, and in most (all?) cases shouldn't be necessary. You're more than likely looking at your static methods being unloaded when the executable ends, and the OS will clean up everything at that point.

If you absolutely need to free up resources, and this object must be shared between all active instances, you could create a reference counter and dispose of the object when you're sure all references have been released. I would put heavy consideration into whether this is the correct approach for you first. New instances would need to verify whether your object is null, and instantiate it again if so.

You shouldn't need to call Dispose. If the class that implements IDisposable uses only managed resources, then those resources will be released as they naturally would when the program completes. If the class uses un-managed resources, then that class should extend CriticalFinalizerObject and free those resources in its finalizer (as well as in its Dispose method).

In other words, proper use of the IDisposable interface doesn't require that Dispose ever be called. It can be called to release managed or un-managed resources at a particular point in the program, but a leak that occurs because of not calling it should be considered a bug.

Edit

What is the C# equivalent of i see myself going away, clean myself up?

In response to your edited question, I think you're looking for the finalizer:

class Foo {
    ~Foo() {
        // Finalizer code. Called when garbage collected, maybe...
    }
}

Be aware, though, that there's no guarantee this method will be called. If you absolutely need it called, you should extend System.Runtime.ConstrainedExecution.CriticalFinalizerObject.

I may still be confused by your question, though. The finalizer is definitely NOT the place to "save my internal values to a file."

The AppDomain Domain Unload Event seems like it would be a good fit for what you're looking for. Since static variables persist until the AppDomain is unloaded, this should give you a hook in right before the variable is destroyed.

You're spending all this time fighting the language, why not redesign so that the problem doesn't exist?

e.g. If you need to save the state of a variable, instead of trying to catch it before it is destroyed, save it each time it is modified, and overwrite the previous state.

Ian Boyd

i had asked this question four times, if four different ways. Phrasing each one slightly different; trying to solve the problem from a different direction. Eventually it was M.A. Hanin that pointed me at this question that solved the problem.

The issue is that there is no single way to know when the domain is shutting down. The best you can do is try to catch various kinds of events that cover you 100% (rounded to the nearest percentage) of the time.

If the code is in some domain besides the default, then use the DomainUnload event. Unfortunately the default AppDomain doesn't raise a DomainUnload event. So then we catch 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();
   }

   ...
}

This has been tested inside a "web-site" an a "WinForms" application.

The more complete code, showing an implementation of 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;
      }
   }
}

Sample usage

From a library that does image processing:

public static class GraphicsLibrary
{
    public Image RotateImage(Image image, Double angleInDegrees)
    {
       Sqm.TimerStart("GraphicaLibrary.RotateImage");
       ...
       Sqm.TimerStop("GraphicaLibrary.RotateImage");
    }
}

From a helper class that can execute a query

public static class DataHelper
{
    public IDataReader ExecuteQuery(IDbConnection conn, String sql)
    {
       Sqm.TimerStart("DataHelper_ExecuteQuery");
       ...
       Sqm.TimerStop("DataHelper_ExecuteQuery");
    }
}

For WinForms themed drawing

public static class ThemeLib
{
   public void DrawButton(Graphics g, Rectangle r, String text)
   {
      Sqm.AddToAverage("ThemeLib/DrawButton/TextLength", text.Length);
   }
}

In a web-site:

private void GetUser(HttpSessionState session)
{
   LoginUser user = (LoginUser)session["currentUser"];

   if (user != null)
      Sqm.Increment("GetUser_UserAlreadyFoundInSession", 1);

   ...
}

In an extension method

/// <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;
}

Note: Any code is released into the public domain. No attribution required.

They will persist for the duration of AppDomain. Changes done to static variable are visible across methods.

MSDN:

If a local variable is declared with the Static keyword, its lifetime is longer than the execution time of the procedure in which it is declared. If the procedure is inside a module, the static variable survives as long as your application continues running.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!