ユーザーは、大量のタイマー セットをスケジュールしたいが、それらのタイマーへの参照を管理したくない場合があります。
ユーザーがタイマーを参照しない場合、タイマーは実行前に GC によって収集される場合があります。
新しく作成されたタイマーのプレースホルダーとして機能するクラス Timers を作成しました。
static class Timers
{
private static readonly ILog _logger = LogManager.GetLogger(typeof(Timers));
private static readonly ConcurrentDictionary<Object, Timer> _timers = new ConcurrentDictionary<Object, Timer>();
/// <summary>
/// Use this class in case you want someone to hold a reference to the timer.
/// Timer without someone referencing it will be collected by the GC even before execution.
/// </summary>
/// <param name="dueTime"></param>
/// <param name="action"></param>
internal static void ScheduleOnce(TimeSpan dueTime, Action action)
{
if (dueTime <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException("dueTime", dueTime, "DueTime can only be greater than zero.");
}
Object obj = new Object();
Timer timer = new Timer(state =>
{
try
{
action();
}
catch (Exception ex)
{
_logger.ErrorFormat("Exception while executing timer. ex: {0}", ex);
}
finally
{
Timer removedTimer;
if (!_timers.TryRemove(obj, out removedTimer))
{
_logger.Error("Failed to remove timer from timers");
}
else
{
removedTimer.Dispose();
}
}
});
if (!_timers.TryAdd(obj, timer))
{
_logger.Error("Failed to add timer to timers");
}
timer.Change(dueTime, TimeSpan.FromMilliseconds(-1));
}
}
削除したタイマーを破棄しないと、メモリ リークが発生します。
タイマーがコレクション
から削除された後、誰かがタイマーのデリゲートへの参照を保持しているようです。_timers
問題は、タイマーを破棄しないとメモリ リークが発生するのはなぜですか?