6

ユーザーは、大量のタイマー セットをスケジュールしたいが、それらのタイマーへの参照を管理したくない場合があります。
ユーザーがタイマーを参照しない場合、タイマーは実行前に 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

問題は、タイマーを破棄しないとメモリ リークが発生するのはなぜですか?

4

2 に答える 2

10

は、タイマー自体によって作成されたTimer によって維持されます。GCHandleこれは、.net メモリ プロファイラーを使用してテストできます。次に、Timerはデリゲートを存続させ、残りを存続させます。

AGCHandleは、ガベージ コレクターを「だまして」到達不能なオブジェクトを存続させるために使用できる特別な種類のオブジェクトです。

以下を使用して、プロファイラーなしで実際にこれをテストできます。

var a = new ClassA();
var timer = new Timer(a.Exec);

var refA = new WeakReference(a);
var refTimer = new WeakReference(timer);

a = null;
timer = null;

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Console.WriteLine(refA.IsAlive);
Console.WriteLine(refTimer.IsAlive);
于 2012-11-03T03:30:26.037 に答える
4

TimersですComponents。そのため、それらを使い終わったら電話Disposeする必要があります。

ドキュメントから:

コンポーネントは、 Finalizeメソッドの暗黙的な呼び出しによる自動メモリ管理を待たずに、 Disposeメソッドの呼び出しによってリソースを明示的に解放する必要があります。Containerが破棄されると、Container内のすべてのコンポーネントも破棄されます。

コンテナを破棄すると、コンテナ内のすべてのコンポーネントも破棄されます」の部分。以下を呼び出すときに、フォームの Dispose メソッドで確認できます。

if (disposing && (components != null))
{
    components.Dispose();
}

そのため、タイマーがコンポーネントに追加されていない限り、タイマーがフォームと共に破棄されるとは思わないでください。

コメントの更新:
タイマーにはアンマネージ コード (OS のタイマー API) へのポインターがあるため、それらが不要になるまで破棄できません。dispose が最初に呼び出されるか、プログラムが終了しない限り、ファイナライザーはオブジェクトに対して実行されません。これは、これらの現在のアンマネージ コードへの参照が原因です。

私が理解していることから、破棄モデルは、アンマネージ コードの実行を許可しながら、プログラムの終了を高速化することを想定していました (ランタイムがダウンタイム中にゴミを収集できるため)。多数の ddl インポートを実行すると、システムがそのように機能する理由がわかり始めます。

ドキュメントには、オブジェクトのファイナライザーから管理対象オブジェクトにアクセスできない可能性があることが示されていることにも注意してください。この例は StreamWriter です。個人的には、これは恣意的なルールだと思いますが、存在するため、破棄システムが必要です。

いずれにせよ、iDisposable インターフェイスを実装するものを使用する場合は、使い終わったら必ず破棄する必要があります。そうすれば、より良い (より一貫性のある) 結果が得られます。

于 2012-11-03T01:27:42.960 に答える