問題
かなり前にタスク マネージャーを監視していてハンドル リークが発生していることに気付いたので、昨日パフォーマンス ログを記録しましたが、修正の優先度は低かったです。これは、10 秒ごとにサンプルを使用して一晩実行します。
時間の制約により、まだこれを実行して失敗したことはありません。また、テスト コンピューターは開発用コンピューターでもあるため、コードを記述しながらこれを実行するのは理想的ではありません。クラッシュするかどうか、いつクラッシュするかはわかりませんが、時間の問題であると強く疑われます。
注:領域内の赤いボックスは、作業ループを「停止」し、短い一時停止の後に再開した場所です。「停止」でスレッド数が ~100 から ~20 に減少しました。約 30 秒後にループが再開されるまで、ハンドルはドロップしませんでした。そのため、いくつかのハンドルが GC されていますが、期待するほど多くはありません。これらすべてのハンドルが収集されるのを妨げているルートや、それらが最初にどこから来たのか (つまり、タスク、GUI、ファイルなど) がわかりません。
この問題の原因がすでにわかっている場合は、これ以上読む必要はありません。この情報とコードの残りの部分は、問題を解決する散弾銃スタイルのアプローチで参照できるように提供しました。根本原因が絞り込まれ次第、削除、編集等を行います。同様に、興味のあるものが欠けている場合はお知らせください。それを提供しようとします (ログ、ダンプなど)。
私がしたこと
私自身、ハンドルの誤用の追跡に関するこのチュートリアルを実行し、ダンプ ファイルを調べて、ハンドルが開いたり閉じたりする場所を見つけました。そして、シンボルをロードするのに苦労したので、ポインターは私には意味不明でした.
リストにある次の2つはまだ行っていませんが、最初にもっと友好的な方法があるかどうか疑問に思いました...
また、これの潜在的な原因であると思われるコードを別の小さなアプリケーションに分割したところ、すべてが問題なくガベージ コレクションされたように見えました (ただし、実行パターンは実際のアプリに比べて大幅に単純化されていました)。
潜在的な犯人
アプリケーションが開いている限り存続するインスタンス化された長寿命のクラスがいくつかあります。これには、それぞれ 1 回だけ作成され、必要に応じて非表示/表示される 5 つのフォームが含まれます。メイン オブジェクトをアプリケーション コントローラーとして使用し、モデルとビューをイベント経由でプレゼンター ファースト パターンのプレゼンターに接続します。
以下は、私がこのアプリケーションで行ういくつかのことですが、重要である場合とそうでない場合があります。
- custom と lambda を広範囲に使用
Action
しFunc
てください。 - イベント用の 3 つのカスタム デリゲート
Task
。非同期実行用に s を生成できます。 - を安全に呼び出すための拡張機能
Controls
。 Task
andParallel.For
/を非常に頻繁に使用して、Parallel.Foreach
ワーカー メソッド (または上記のイベント) を実行します。- Thread.Sleep() を使用しないでください。代わりに、AutoResetEvent を使用するカスタム Sleep.For() を使用してください。
メインループ
実行中のこのアプリケーションの一般的なフローは、オフラインバージョンでは一連のファイルのループに基づいており、オンラインバージョンではデジタル入力信号のポーリングに基づいています。以下は、オフラインバージョンのコメント付きの sudo コードです。これは、外部ハードウェアを必要とせずにラップトップから実行できるものであり、上のグラフが監視しているものです (現時点では、オンラインモードのハードウェアにアクセスできません)。)。
public void foo()
{
// Sudo Code
var InfiniteReplay = true;
var Stopped = new CancellationToken();
var FileList = new List<string>();
var AutoMode = new ManualResetEvent(false);
var CompleteSignal = new ManualResetEvent(false);
Action<CancellationToken> PauseIfRequired = (tkn) => { };
// Enumerate a Directory...
// ... Load each file and do work
do
{
foreach (var File in FileList)
{
/// Method stops the loop waiting on a local AutoResetEvent
/// if the CompleteSignal returns faster than the
/// desired working rate of ~2 seconds
PauseIfRequired(Stopped);
/// While not 'Stopped', poll for Automatic Mode
/// NOTE: This mimics how the online system polls a digital
/// input instead of a ManualResetEvent.
while (!Stopped.IsCancellationRequested)
{
if (AutoMode.WaitOne(100))
{
/// Class level Field as the Interface did not allow
/// for passing the string with the event below
m_nextFile = File;
// Raises Event async using Task.Factory.StartNew() extension
m_acquireData.Raise();
break;
}
}
// Escape if Canceled
if (Stopped.IsCancellationRequested)
break;
// If In Automatic Mode, Wait for Complete Signal
if (AutoMode.WaitOne(0))
{
// Ensure Signal Transition
CompleteSignal.WaitOne(0);
if (!CompleteSignal.WaitOne(10000))
{
// Log timeout and warn User after 10 seconds, then continue looping
}
}
}
// Keep looping through same set of files until 'Stopped' if in Infinite Replay Mode
} while (!Stopped.IsCancellationRequested && InfiniteReplay);
}
非同期イベント
以下はイベントの拡張で、ほとんどはデフォルトの非同期オプションを使用して実行されます。'TryRaising()' 拡張機能は、デリゲートを try-catch でラップし、例外をログに記録するだけです (デリゲートは再スローしませんが、通常のプログラム フローの一部ではありません)。
using System.Threading.Tasks;
using System;
namespace Common.EventDelegates
{
public delegate void TriggerEvent();
public delegate void ValueEvent<T>(T p_value) where T : struct;
public delegate void ReferenceEvent<T>(T p_reference);
public static partial class DelegateExtensions
{
public static void Raise(this TriggerEvent p_response, bool p_synchronized = false)
{
if (p_response == null)
return;
if (!p_synchronized)
Task.Factory.StartNew(() => { p_response.TryRaising(); });
else
p_response.TryRaising();
}
public static void Broadcast<T>(this ValueEvent<T> p_response, T p_value, bool p_synchronized = false)
where T : struct
{
if (p_response == null)
return;
if (!p_synchronized)
Task.Factory.StartNew(() => { p_response.TryBroadcasting(p_value); });
else
p_response.TryBroadcasting(p_value);
}
public static void Send<T>(this ReferenceEvent<T> p_response, T p_reference, bool p_synchronized = false)
where T : class
{
if (p_response == null)
return;
if (!p_synchronized)
Task.Factory.StartNew(() => { p_response.TrySending(p_reference); });
else
p_response.TrySending(p_reference);
}
}
}
GUI セーフインボーク
using System;
using System.Windows.Forms;
using Common.FluentValidation;
using Common.Environment;
namespace Common.Extensions
{
public static class InvokeExtensions
{
/// <summary>
/// Execute a method on the control's owning thread.
/// </summary>
/// http://stackoverflow.com/q/714666
public static void SafeInvoke(this Control p_control, Action p_action, bool p_forceSynchronous = false)
{
p_control
.CannotBeNull("p_control");
if (p_control.InvokeRequired)
{
if (p_forceSynchronous)
p_control.Invoke((Action)delegate { SafeInvoke(p_control, p_action, p_forceSynchronous); });
else
p_control.BeginInvoke((Action)delegate { SafeInvoke(p_control, p_action, p_forceSynchronous); });
}
else
{
if (!p_control.IsHandleCreated)
{
// The user is responsible for ensuring that the control has a valid handle
throw
new
InvalidOperationException("SafeInvoke on \"" + p_control.Name + "\" failed because the control had no handle.");
/// jwdebug
/// Only manually create handles when knowingly on the GUI thread
/// Add the line below to generate a handle http://stackoverflow.com/a/3289692/1718702
//var h = this.Handle;
}
if (p_control.IsDisposed)
throw
new
ObjectDisposedException("Control is already disposed.");
p_action.Invoke();
}
}
}
}
Sleep.For()
using System.Threading;
using Common.FluentValidation;
namespace Common.Environment
{
public static partial class Sleep
{
public static bool For(int p_milliseconds, CancellationToken p_cancelToken = default(CancellationToken))
{
// Used as "No-Op" during debug
if (p_milliseconds == 0)
return false;
// Validate
p_milliseconds
.MustBeEqualOrAbove(0, "p_milliseconds");
// Exit immediate if cancelled
if (p_cancelToken != default(CancellationToken))
if (p_cancelToken.IsCancellationRequested)
return true;
var SleepTimer =
new AutoResetEvent(false);
// Cancellation Callback Action
if (p_cancelToken != default(CancellationToken))
p_cancelToken
.Register(() => SleepTimer.Set());
// Block on SleepTimer
var Canceled = SleepTimer.WaitOne(p_milliseconds);
return Canceled;
}
}
}