さて、ここで3つのこと...
まず、.NET のサーバー GC の使用を開始します: http://msdn.microsoft.com/en-us/library/ms229357.aspx。これにより、おそらくアプリケーションがブロックされないようになります。
次に、VM でそれを実行できる場合: 更新プログラムを確認します。これは常に明らかなように思えますが、単純な Windows の更新で奇妙な問題が修正されるケースを数多く見てきました。
3 番目に、ここで問題の 1 つである可能性があるオブジェクトの有効期間について指摘したいと思います。これは何が起こるかというかなり長い話なので、我慢してください。
オブジェクトの寿命は、基本的に構築 - ガベージ コレクション - ファイナライズです。3 つのプロセスはすべて別のスレッドで実行されます。GC は、「デストラクタ」を呼び出すキューを持つファイナライズ スレッドにデータを渡します。
奇妙なことをするファイナライザーがある場合は、次のように言います。
public class FinalizerObject
{
public FinalizerObject(int n)
{
Console.WriteLine("Constructed {0}", n);
this.n = n;
}
private int n;
~FinalizerObject()
{
while (true) { Console.WriteLine("Finalizing {0}...", n); System.Threading.Thread.Sleep(1000); }
}
}
ファイナライザーはキューを処理する別のスレッドで実行されるため、1 つのファイナライザーで愚かなことを行うと、アプリケーションにとって深刻な問題になります。上記のクラスを 2 回使用すると、これを確認できます。
static void Main(string[] args)
{
SomeMethod();
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
Console.WriteLine("All done.");
Console.ReadLine();
}
static void SomeMethod()
{
var obj2 = new FinalizerObject(1);
var obj3 = new FinalizerObject(2);
}
メイン スレッドがまだ応答しているにもかかわらず、小さなメモリ リークが発生し、CPU プロセスが 100% の状態で Thread.Sleep を削除した場合に注意してください。これらは異なるスレッドであるため、ここからはプロセス全体を非常に簡単にブロックできます。たとえば、ロックを使用します。
static void Main(string[] args)
{
SomeMethod();
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
Thread.Sleep(1000);
lock (lockObject)
{
Console.WriteLine("All done.");
}
Console.ReadLine();
}
static object lockObject = new Program();
static void SomeMethod()
{
var obj2 = new FinalizerObject(1, lockObject);
var obj3 = new FinalizerObject(2, lockObject);
}
[...]
~FinalizerObject()
{
lock (lockObject) { while (true) { Console.WriteLine("Finalizing {0}...", n); System.Threading.Thread.Sleep(1000); } }
}
だから、「本気ですか?」と考えているのがわかります。実際には、これに気付かずにこのようなことをしている可能性があります。これが「収量」の出番です。
「yield」の IEnumerable は実際には IDisposable であるため、IDisposable パターンを実装します。「yield」実装をロックと組み合わせ、「MoveNext」などで列挙して IDisposable を呼び出すのを忘れると、上記を反映したかなり厄介な動作が発生します。特に、ファイナライザーは別のスレッド (!) によってファイナライズ キューから呼び出されるためです。それを無限ループまたはスレッドの安全でないコードと組み合わせると、かなり厄介な予期しない動作が発生し、例外的な場合 (メモリが不足したとき、または GC が何かを行う必要があるとき) にトリガーされます。
言い換えれば、私はあなたのディスポーザブルとファイナライザーをチェックし、それらについて非常に批判的です. 「yield」に暗黙的なファイナライザーがあるかどうかを確認し、同じスレッドから IDisposable を呼び出していることを確認してください。あなたが警戒しなければならないことのいくつかの例:
try
{
for (int i = 0; i < 10; ++i)
{
yield return "foo";
}
}
finally
{
// Called by IDisposable
}
と
lock (myLock) // 'lock' and 'using' also trigger IDisposable
{
yield return "foo";
}