|
using System;
using System.Collections.Generic;
using System.Threading;
using Pfz.Extensions.MonitorLockExtensions;
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;
using(keepedObjects.LockWithTimeout())
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;
using(keepedObjects.LockWithTimeout())
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;
List<WeakDelegateBase> listToRun = null;
try
{
List<List<WeakDelegateBase>> availLater = new List<List<WeakDelegateBase>>();
lock(fCollectedLock)
{
var oldCollected = fCollected;
listToRun = new List<WeakDelegateBase>(oldCollected.Count);
var newCollected = new Dictionary<int, List<WeakDelegateBase>>(oldCollected.Count);
foreach(var pair in oldCollected)
{
var oldList = pair.Value;
using(var oldListLockDisposer = oldList.TryLockWithTimeout())
{
if (oldListLockDisposer == null || oldList.Count > 0)
{
newCollected.Add(pair.Key, oldList);
availLater.Add(oldList);
}
}
}
fCollected = newCollected;
}
foreach(var list in availLater)
{
lock(list)
{
int count = list.Count;
for(int i=count-1; i>=0; i--)
{
WeakDelegateBase weakDelegate = list[i];
if (weakDelegate.Target == null && !weakDelegate.Method.IsStatic)
list.RemoveAt(i);
else
listToRun.Add(weakDelegate);
}
list.TrimExcess();
}
}
}
catch
{
}
// we use the listToRun instead of a foreach in each list as an way to avoid creating many objects,
// which could (in very rare circunstances) throw an exception.
// If an exception was thrown, no problem. If we have any item in
// list, will try to run such item.
if (listToRun != null)
{
int countListToRun = listToRun.Count;
for (int i=0; i<countListToRun; i++)
{
WeakDelegateBase action = listToRun[i];
action.Invoke(null);
}
}
}
}
#endregion
#region Collected
private static volatile Dictionary<int, List<WeakDelegateBase>> fCollected = new Dictionary<int, List<WeakDelegateBase>>();
private static object fCollectedLock = new object();
/// <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();
List<WeakDelegateBase> list;
using(fCollectedLock.LockWithTimeout())
{
var collected = fCollected;
// if there is no item with the same hashCode, we
// can insert a new one directly.
if (!collected.TryGetValue(hashCode, out list))
{
WeakDelegateBase weakDelegate = new WeakDelegateBase(value);
list = new List<WeakDelegateBase>(1);
list.Add(weakDelegate);
collected.Add(hashCode, list);
return;
}
}
// ok, an item with the same hashCode exists, so
// first we check if this item is already in the list.
// if there is, we simple return.
using(list.LockWithTimeout())
{
foreach(WeakDelegateBase action in list)
if (action.Target == value.Target && action.Method == value.Method)
return;
WeakDelegateBase weakDelegate = new WeakDelegateBase(value);
list.Add(weakDelegate);
}
}
remove
{
int hashCode = value.GetHashCode();
List<WeakDelegateBase> list;
using(fCollectedLock.LockWithTimeout())
{
var collected = fCollected;
if (!collected.TryGetValue(hashCode, out list))
return;
}
using(list.LockWithTimeout())
{
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).