|
using System;
using System.Collections.Generic;
using System.Threading;
namespace Pfz.Caching
{
/// <summary>
/// Some methods and events to interact with garbage collection. You can
/// keep an object alive during the next collection or register to know
/// when a collection has just happened. This is useful if you don't use
/// WeakReferences, but know how to free memory if needed. For example,
/// you can do a TrimExcess to your lists to free some memory.
///
/// Caution: GC.KeepAlive keeps the object alive until that line of code,
/// while GCUtils.KeepAlive keeps the object alive until the next
/// generation.
/// </summary>
public static class GCUtils
{
#region Constructor
private static bool fFinished;
private static ManualResetEvent fCollectedEvent = new ManualResetEvent(false);
static GCUtils()
{
AppDomain current = AppDomain.CurrentDomain;
current.DomainUnload += new EventHandler(p_DomainUnload);
current.ProcessExit += new EventHandler(p_ProcessExit);
new Runner();
Thread thread = new Thread(p_ExecuteCollected);
thread.Name = "Pfz.GCUtils.Collected executor thread.";
thread.Start();
}
#endregion
#region Finalization event handles
private static void p_ProcessExit(object sender, EventArgs e)
{
fFinished = true;
fCollectedEvent.Set();
}
private static void p_DomainUnload(object sender, EventArgs e)
{
fFinished = true;
fCollectedEvent.Set();
}
#endregion
#region KeepAlive
private static HashSet<object> fKeepedObjects = new HashSet<object>(fReferenceComparer);
/// <summary>
/// Keeps an object alive at the next collection. This is useful for WeakReferences as an way
/// to guarantee that recently used objects will not be immediatelly collected. At the next
/// generation, you can call KeepAlive again, making the object alive for another generation.
/// </summary>
/// <param name="item"></param>
public static void KeepAlive(object item)
{
if (item == null)
return;
var keepedObjects = fKeepedObjects;
lock(keepedObjects)
keepedObjects.Add(item);
}
/// <summary>
/// Expires an object. Is the opposite of KeepAlive.
/// </summary>
/// <param name="item"></param>
/// <returns>true if the object was in the KeepAlive list, false otherwise.</returns>
public static bool Expire(object item)
{
if (item == null)
return false;
var keepedObjects = fKeepedObjects;
lock (keepedObjects)
return keepedObjects.Remove(item);
}
#endregion
#region p_ExecuteCollected
private static void p_ExecuteCollected()
{
var thread = Thread.CurrentThread;
while(true)
{
// we are background while waiting.
thread.IsBackground = true;
fCollectedEvent.WaitOne();
if (fFinished)
return;
fCollectedEvent.Reset();
// but we are not background while running.
thread.IsBackground = false;
var collected = fCollected;
lock(collected)
{
// this is the best trimexcess we can do.
fCollected = new Dictionary<int, List<WeakDelegateBase>>();
foreach(List<WeakDelegateBase> list in collected.Values)
foreach(WeakDelegateBase action in list)
action.Invoke(null);
}
}
}
#endregion
#region Collected
private static Dictionary<int, List<WeakDelegateBase>> fCollected = new Dictionary<int, List<WeakDelegateBase>>();
/// <summary>
/// This event is called after a GarbageCollection has just finished,
/// in another thread. As this happens after the collection has finished,
/// all other threads are running too, so you must guarantee that
/// your event is thread safe.
/// </summary>
public static event Action Collected
{
add
{
int hashCode = value.GetHashCode();
var collected = fCollected;
lock(collected)
{
List<WeakDelegateBase> list;
// if there is no item with the same hashCode, we
// can insert a new one directly.
if (collected.TryGetValue(hashCode, out list))
{
// ok, an item with the same hashCode exists, so
// first we check if this is already in the list.
// if there is, we simple return.
foreach(WeakDelegateBase action in list)
if (action.Target == value.Target && action.Method == value.Method)
return;
}
else
{
list = new List<WeakDelegateBase>(1);
collected.Add(hashCode, list);
}
WeakDelegateBase weakDelegate = new WeakDelegateBase(value);
list.Add(weakDelegate);
}
}
remove
{
int hashCode = value.GetHashCode();
var collected = fCollected;
lock(collected)
{
List<WeakDelegateBase> list;
if (!collected.TryGetValue(hashCode, out list))
return;
int count = list.Count;
for(int i=0; i<count; i++)
{
WeakDelegateBase weakDelegate = list[i];
if (weakDelegate.Method == value.Method && weakDelegate.Target == value.Target)
{
list.RemoveAt(i);
return;
}
}
}
}
}
#endregion
#region Runner - nested class
private sealed class Runner
{
~Runner()
{
// If we don't test, we will keep re-registering forever
// when the application is finishing.
if (fFinished)
return;
GC.ReRegisterForFinalize(this);
// no lock is needed as we simple put a new object and don't
// even try to read the old object.
fKeepedObjects = new HashSet<object>(fReferenceComparer);
fCollectedEvent.Set();
}
}
#endregion
#region ReferenceComparer - nested class
private static readonly ReferenceComparer fReferenceComparer = new ReferenceComparer();
/// <summary>
/// Class used to compare two references.
/// They must point to the same instance (not an equal instance) to be
/// considered equal.
/// This also helps making comparisons faster for objects that
/// implement Equals.
/// </summary>
private sealed class ReferenceComparer:
IEqualityComparer<object>
{
bool IEqualityComparer<object>.Equals(object x, object y)
{
return x == y;
}
int IEqualityComparer<object>.GetHashCode(object obj)
{
return obj.GetHashCode();
}
}
#endregion
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.
At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.
Want more info or simply want to contact me?
Take a look at:
http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com
Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).