[解決済み] System.Timers.Timer は GC に耐えられるのに、System.Threading.Timer は耐えられな いのはなぜですか?
質問
どうやら
System.Timers.Timer
インスタンスは何らかのメカニズムで生かされているようですが
System.Threading.Timer
インスタンスはそうではありません。
サンプルプログラムでは、周期的な
System.Threading.Timer
とオートリセット
System.Timers.Timer
:
class Program
{
static void Main(string[] args)
{
var timer1 = new System.Threading.Timer(
_ => Console.WriteLine("Stayin alive (1)..."),
null,
0,
400);
var timer2 = new System.Timers.Timer
{
Interval = 400,
AutoReset = true
};
timer2.Elapsed += (_, __) => Console.WriteLine("Stayin alive (2)...");
timer2.Enabled = true;
System.Threading.Thread.Sleep(2000);
Console.WriteLine("Invoking GC.Collect...");
GC.Collect();
Console.ReadKey();
}
}
このプログラム(.NET 4.0 Client, Release, デバッガ外)を実行したところ、(1)のように
System.Threading.Timer
だけがGCされます。
Stayin alive (1)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Invoking GC.Collect...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
EDIT : 下記のジョンさんの回答は受け入れましたが、もう少し詳しく説明したいと思います。
上記のサンプルプログラムを実行する際(ブレークポイントを
Sleep
にブレークポイントを設定) を実行すると、以下のように、当該オブジェクトの状態と
GCHandle
テーブルの状態を示します。
!dso
OS Thread Id: 0x838 (2104)
ESP/REG Object Name
0012F03C 00c2bee4 System.Object[] (System.String[])
0012F040 00c2bfb0 System.Timers.Timer
0012F17C 00c2bee4 System.Object[] (System.String[])
0012F184 00c2c034 System.Threading.Timer
0012F3A8 00c2bf30 System.Threading.TimerCallback
0012F3AC 00c2c008 System.Timers.ElapsedEventHandler
0012F3BC 00c2bfb0 System.Timers.Timer
0012F3C0 00c2bfb0 System.Timers.Timer
0012F3C4 00c2bfb0 System.Timers.Timer
0012F3C8 00c2bf50 System.Threading.Timer
0012F3CC 00c2bfb0 System.Timers.Timer
0012F3D0 00c2bfb0 System.Timers.Timer
0012F3D4 00c2bf50 System.Threading.Timer
0012F3D8 00c2bee4 System.Object[] (System.String[])
0012F4C4 00c2bee4 System.Object[] (System.String[])
0012F66C 00c2bee4 System.Object[] (System.String[])
0012F6A0 00c2bee4 System.Object[] (System.String[])
!gcroot -nostacks 00c2bf50
!gcroot -nostacks 00c2c034
DOMAIN(0015DC38):HANDLE(Strong):9911c0:Root: 00c2c05c(System.Threading._TimerCallback)->
00c2bfe8(System.Threading.TimerCallback)->
00c2bfb0(System.Timers.Timer)->
00c2c034(System.Threading.Timer)
!gchandles
GC Handle Statistics:
Strong Handles: 22
Pinned Handles: 5
Async Pinned Handles: 0
Ref Count Handles: 0
Weak Long Handles: 0
Weak Short Handles: 0
Other Handles: 0
Statistics:
MT Count TotalSize Class Name
7aa132b4 1 12 System.Diagnostics.TraceListenerCollection
79b9f720 1 12 System.Object
79ba1c50 1 28 System.SharedStatics
79ba37a8 1 36 System.Security.PermissionSet
79baa940 2 40 System.Threading._TimerCallback
79b9ff20 1 84 System.ExecutionEngineException
79b9fed4 1 84 System.StackOverflowException
79b9fe88 1 84 System.OutOfMemoryException
79b9fd44 1 84 System.Exception
7aa131b0 2 96 System.Diagnostics.DefaultTraceListener
79ba1000 1 112 System.AppDomain
79ba0104 3 144 System.Threading.Thread
79b9ff6c 2 168 System.Threading.ThreadAbortException
79b56d60 9 17128 System.Object[]
Total 27 objects
Johnが回答で指摘したように、どちらのタイマーもコールバックを登録します(
System.Threading._TimerCallback
) を
GCHandle
テーブルの中にあります。Hans がコメントで指摘したように
state
パラメータは、これが実行されたときにも生かされます。
Johnが指摘したように、なぜ
System.Timers.Timer
が生かされているのは、コールバックによって参照されるからです (これは
state
パラメータとして渡されます。
System.Threading.Timer
); 同様に、私たちの
System.Threading.Timer
がGCされるのは、それが
ではなく
によって参照されるからです。
への明示的な参照を追加する
timer1
のコールバック(例.
Console.WriteLine("Stayin alive (" + timer1.GetType().FullName + ")")
) は、GC を防ぐのに十分です。
のシングルパラメータコンストラクタを使用すると
System.Threading.Timer
を使ってもうまくいきます。なぜなら、タイマーは自分自身を
state
パラメータとして自分自身を参照するからです。次のコードは、GCの後、両方のタイマーを生かします。
GCHandle
テーブルからのコールバックによって参照されるからです。
class Program
{
static void Main(string[] args)
{
System.Threading.Timer timer1 = null;
timer1 = new System.Threading.Timer(_ => Console.WriteLine("Stayin alive (1)..."));
timer1.Change(0, 400);
var timer2 = new System.Timers.Timer
{
Interval = 400,
AutoReset = true
};
timer2.Elapsed += (_, __) => Console.WriteLine("Stayin alive (2)...");
timer2.Enabled = true;
System.Threading.Thread.Sleep(2000);
Console.WriteLine("Invoking GC.Collect...");
GC.Collect();
Console.ReadKey();
}
}
どのように解決するのですか?
この質問と同様の質問に答えるには、windbg、sos、および
!gcroot
0:008> !gcroot -nostacks 0000000002354160
DOMAIN(00000000002FE6A0):HANDLE(Strong):241320:Root:00000000023541a8(System.Thre
ading._TimerCallback)->
00000000023540c8(System.Threading.TimerCallback)->
0000000002354050(System.Timers.Timer)->
0000000002354160(System.Threading.Timer)
0:008>
どちらの場合も、ネイティブタイマーはコールバックオブジェクトのGCを(GCHandleを介して)防止する必要があります。違いは
System.Timers.Timer
の場合、コールバックは
System.Timers.Timer
オブジェクトを参照します (これは内部で
System.Threading.Timer
)
関連
-
[解決済み】コンパイラーエラーメッセージ。コンパイラはエラーコード -532462766 で失敗しました。
-
[解決済み] 名前 'ConfigurationManager' は、現在のコンテキストに存在しません。
-
[解決済み] gacutil.exeはどこですか?
-
[解決済み] 列挙型を文字列に変換する
-
[解決済み] なぜList<T>を継承しないのですか?
-
[解決済み] System.Timers.Timer vs System.Threading.Timer
-
[解決済み] Math.Round(2.5)はなぜ3でなく2を返すのですか?
-
[解決済み] HttpClientのBaseAddressが機能しないのはなぜですか?
-
[解決済み】Boolean.ToStringが "True "と出力し、"true "と出力しない理由
-
[解決済み] C#のSystem.Threading.Timerが動作していないようです。3秒に一度、高速に動作します。
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] [Solved] ファイル *.mdf をデータベースとしてアタッチできない
-
[解決済み] VS2017/2015 で .xproj ファイルを開く方法
-
[解決済み] Windowsイベントログで参照される「フレームワークのバージョン」とは何ですか?
-
[解決済み] csilogfileは何のためにあるのですか?
-
[解決済み] .NET WebRequestを使用してsharepointにファイルをアップロードすると、409/Conflict HTTPエラーが発生する理由?
-
[解決済み] Nuget接続の試行に失敗しました。"Unable to load service index for source"。
-
[解決済み] terminationGracePeriodSeconds not
-
[解決済み] 文字列から数字を抽出する正規表現
-
[解決済み] MemoryStreamから文字列を取得する方法は?
-
[解決済み] Visual Studioの「Any CPU」ターゲットはどういう意味ですか?