次の信頼性規則は SQL Server 向けです。ただし、ホスト ベースのサーバー アプリケーションにも適用されます。 SQL Server などのサーバーがリソースをリークせず、停止しないことが非常に重要です。 ただし、これは、オブジェクトの状態を変更するすべてのメソッドのバックアウト コードを記述することによって行うことはできません。 目標は、バックアウト コードを使用して、すべての場所のエラーから回復する 100% の信頼性の高いマネージド コードを記述することです。 それは成功の可能性がほとんどない困難な作業です。 共通言語ランタイム (CLR) は、完全なコードの記述を可能にするために、マネージド コードに十分な強力な保証を簡単に提供することはできません。 ASP.NET とは異なり、SQL Server では、データベースを長時間停止しないとリサイクルできないプロセスが 1 つだけ使用されることに注意してください。
これらの弱い保証と単一プロセスでの実行により、信頼性は、必要に応じてスレッドの終了またはアプリケーション ドメインのリサイクルに基づいており、ハンドルやメモリなどのオペレーティング システム リソースがリークしないように予防措置を講じる必要があります。 このより単純な信頼性制約を使用しても、信頼性に関する重要な要件が引き続き存在します。
オペレーティング システムのリソースをリークしません。
CLR に対するすべてのフォームのすべてのマネージド ロックを識別します。
アプリケーションドメイン間の共有状態を中断しないでください。これにより、 AppDomain リサイクルがスムーズに機能します。
理論的には、 ThreadAbortException、 StackOverflowException、および OutOfMemoryException の例外を処理するマネージド コードを記述することは可能ですが、開発者がアプリケーション全体でこのような堅牢なコードを記述することは理に障隶できません。 このため、帯域外例外により、実行中のスレッドが終了します。終了したスレッドが共有状態を編集していた場合(スレッドがロックを保持しているかどうかによって判断できる)、 AppDomain はアンロードされます。 共有状態を編集しているメソッドが終了すると、共有状態に更新するための信頼性の高いバックアウト コードを記述できないため、状態が破損します。
.NET Framework バージョン 2.0 では、信頼性を必要とする唯一のホストは SQL Server です。 アセンブリが SQL Server で実行される場合は、データベースで実行するときに無効になっている特定の機能がある場合でも、そのアセンブリのすべての部分に対して信頼性の作業を行う必要があります。 これは、コード分析エンジンがアセンブリ レベルでコードを調べ、無効なコードを区別できないために必要です。 もう 1 つの SQL Server プログラミング上の考慮事項は、SQL Server が 1 つのプロセスですべてを実行し、メモリやオペレーティング システムハンドルなどのすべてのリソースをクリーンアップするために AppDomain リサイクルが使用されるということです。
バックアウト コードのファイナライザーまたはデストラクターまたは try/finally
ブロックに依存することはできません。 割り込まれたり、呼び出されなかったりする可能性があります。
非同期例外は、 ThreadAbortException、 StackOverflowException、 OutOfMemoryExceptionなど、すべてのマシン命令で予期しない場所でスローされる可能性があります。
マネージド スレッドは、必ずしも SQL の Win32 スレッドであるとは限りません。繊維である可能性があります。
プロセス全体またはアプリケーション間のドメイン変更可能な共有状態は、安全に変更することは非常に困難であり、可能な限り避ける必要があります。
SQL Server では、メモリ不足の状態はまれではありません。
SQL Server でホストされているライブラリが共有状態を正しく更新しない場合、データベースが再起動されるまでコードが回復しない可能性が高くなります。 さらに、極端な場合には、SQL Server プロセスが失敗し、データベースが再起動する可能性があります。 データベースを再起動すると、Web サイトが停止したり、会社の運用に影響を与えたりして、可用性が損なわれる可能性があります。 メモリやハンドルなどのオペレーティング システム リソースのリークが遅いと、サーバーが最終的に復旧の可能性なくハンドルの割り当てに失敗したり、サーバーのパフォーマンスが低下し、お客様のアプリケーションの可用性が低下したりする可能性があります。 明らかに、これらのシナリオを回避したいと考えています。
ベスト プラクティス ルール
この概要では、フレームワークの安定性と信頼性を向上させるために、サーバーで実行されるマネージド コードのコード レビューでキャッチする必要がある内容に重点を置いて説明しました。 これらのチェックはすべて一般的に良い習慣であり、サーバー上で絶対に行う必要があります。
デッド ロックまたはリソース制約が発生した場合、SQL Server はスレッドを中止するか、 AppDomainを破棄します。 この場合、制約付き実行リージョン (CER) 内のバックアウト コードのみが実行されていることが保証されます。
SafeHandle を使用してリソース リークを回避する
AppDomainアンロードの場合は、finally
ブロックまたはファイナライザーが実行されることに依存できないため、IntPtr、HandleRef、または同様のクラスではなく、SafeHandle クラスを介してすべてのオペレーティング システム リソース アクセスを抽象化することが重要です。 これにより、CLR は、 AppDomain の破棄ケースでも使用するハンドルを追跡して閉じることができます。
SafeHandle は、CLR が常に実行する重要なファイナライザーを使用します。
オペレーティング システム ハンドルは、作成された時点から解放された時点までのセーフ ハンドルに格納されます。 ハンドルをリークする ThreadAbortException が発生する可能性があるウィンドウはありません。 さらに、プラットフォーム呼び出しはハンドルを参照カウントします。これにより、ハンドルの有効期間を厳密に追跡できるため、 Dispose
と現在ハンドルを使用しているメソッドの間の競合状態に関するセキュリティの問題が回避されます。
現在、オペレーティング システム ハンドルをクリーンアップするファイナライザーがあるほとんどのクラスでは、ファイナライザーは不要になります。 代わりに、ファイナライザーは SafeHandle 派生クラス上にあります。
SafeHandleはIDisposable.Disposeの代わりではないことに注意してください。 オペレーティング システム リソースを明示的に破棄する場合、リソースの競合とパフォーマンスの利点が引き続き発生する可能性があります。 リソースを明示的に破棄する finally
ブロックが完了まで実行されない可能性があることを認識するだけです。
SafeHandle を使用すると、オペレーティング システム ハンドルの解放ルーチンへの状態の渡しやループ内のハンドルのセットの解放など、ハンドルを解放する処理を実行する独自の ReleaseHandle メソッドを実装できます。 CLR では、このメソッドが実行されていることが保証されます。 ハンドルがすべての状況で確実に解放されるようにするのは、 ReleaseHandle 実装の作成者の責任です。 これを行わないとハンドルがリークされ、ハンドルに関連付けられているネイティブ リソースが漏えいすることがよくあります。 したがって、ReleaseHandle実装で呼び出し時に使用できないリソースを割り当てる必要がないように、派生クラスSafeHandle構造することが重要です。 コードでこのようなエラーを処理し、コントラクトを完了してネイティブ ハンドルを解放できる場合は、 ReleaseHandle の実装内で失敗する可能性があるメソッドを呼び出すことができます。 デバッグの目的で、 ReleaseHandle には Boolean 戻り値があります。この戻り値は、リソースの解放を妨げる致命的なエラーが発生した場合に false
に設定される可能性があります。 これにより、問題の特定に役立つ releaseHandleFailed MDA (有効な場合) がアクティブになります。 他の方法ではランタイムには影響しません。 ReleaseHandle は同じリソースに対して再度呼び出されないため、ハンドルがリークされます。
SafeHandle は、特定のコンテキストでは適切ではありません。 ReleaseHandle メソッドはGCファイナライザー スレッドで実行できるため、特定のスレッドで解放する必要があるハンドルは、SafeHandleでラップしないでください。
ランタイム呼び出し可能ラッパー (RCW) は、追加コードなしで CLR によってクリーンアップできます。 プラットフォーム呼び出しを使用し、COM オブジェクトを IUnknown*
または IntPtrとして扱うコードの場合は、RCW を使用するようにコードを書き直す必要があります。
SafeHandle アンマネージド リリース メソッドがマネージド コードに呼び出される可能性があるため、このシナリオには適していない可能性があります。
コード分析ルール
オペレーティング システム リソースをカプセル化するには、 SafeHandle を使用します。 HandleRefまたはIntPtr型のフィールドは使用しないでください。
オペレーティング システム リソースのリークを防ぐためにファイナライザーを実行する必要がないことを確認する
ファイナライザーを慎重に確認して、実行されていない場合でも、重要なオペレーティング システム リソースがリークされていないことを確認します。 アプリケーションが安定した状態で実行されている場合や SQL Server などのサーバーがシャットダウンした場合、通常の AppDomain アンロードとは異なり、突然の AppDomain アンロード中にオブジェクトは終了しません。 アプリケーションの正確性を保証することはできませんが、リソースをリークしないことによってサーバーの整合性を維持する必要があるため、突然アンロードされた場合にリソースがリークされないようにします。 オペレーティング システム リソースを解放するには、 SafeHandle を使用します。
オペレーティング システム リソースのリークを防ぐために finally 句を実行する必要がないことを確認します
finally
句が CER の外部で実行されるとは限らないので、ライブラリ開発者はアンマネージ リソースを解放するために、 finally
ブロック内のコードに依存しないようにする必要があります。
SafeHandleを使用することをお勧めします。
コード分析ルール
Finalize
ではなく、オペレーティング システム リソースをクリーンアップするためにSafeHandleを使用します。
IntPtrは使用しないでください。SafeHandleを使用してリソースをカプセル化します。 finally 句を実行する必要がある場合は、CER に配置します。
すべてのロックは、既存のマネージド ロック コードを通過する必要があります
CLR は、コードがロックされているタイミングを認識して、スレッドを中止するのではなく、 AppDomain を破棄する必要があります。 スレッドによって操作されるデータが不整合な状態のままになる可能性があり、スレッドの中止は危険な場合があります。 したがって、 AppDomain 全体をリサイクルする必要があります。 ロックの識別に失敗した結果は、デッドロックまたは正しくない結果のいずれかになります。 BeginCriticalRegionメソッドとEndCriticalRegionメソッドを使用して、ロック領域を識別します。 現在のスレッドにのみ適用される Thread クラスの静的メソッドであり、1 つのスレッドが別のスレッドのロックカウントを編集するのを防ぐのに役立ちます。
Enter この CLR 通知 Exit 組み込まれているので、使用することをお勧めします。また、これらのメソッドを使用する lock ステートメントを使用することをお勧めします。
スピン ロックや AutoResetEvent などの他のロック メカニズムでは、これらのメソッドを呼び出して、重要なセクションが入力されていることを CLR に通知する必要があります。 これらのメソッドはロックを受け取りません。コードがクリティカル セクションで実行され、スレッドを中止すると共有状態に不整合が残る可能性があることを CLR に通知します。 カスタム ReaderWriterLock クラスなど、独自のロックの種類を定義した場合は、これらのロック カウント メソッドを使用します。
コード分析ルール
BeginCriticalRegionとEndCriticalRegionを使用して、すべてのロックをマークして識別します。 ループ内で CompareExchange、 Increment、および Decrement を使用しないでください。 これらのメソッドの Win32 バリアントのプラットフォーム呼び出しは行わないでください。 ループ内で Sleep を使用しないでください。 揮発性フィールドは使用しないでください。
クリーンアップ コードは finally ブロックまたは catch ブロックに含まれる必要があります。catch に従わない
クリーンアップ コードは、 catch
ブロックに従うべきではありません。 finally
または catch
ブロック自体に含まれている必要があります。 これは通常の良い方法です。
finally
ブロックは、例外がスローされたときと、try
ブロックの末尾が通常発生したときに同じコードを実行するため、一般的に推奨されます。
ThreadAbortExceptionなど、予期しない例外がスローされた場合、クリーンアップ コードは実行されません。
finally
でクリーンアップするアンマネージ リソースは、リークを防ぐために、SafeHandleにラップするのが理想的です。 C# using
キーワードは、ハンドルを含むオブジェクトを破棄するために効果的に使用できることに注意してください。
AppDomainリサイクルではファイナライザー スレッド上のリソースをクリーンアップできますが、クリーンアップ コードを適切な場所に配置することが重要です。 スレッドがロックを保持せずに非同期例外を受け取った場合、CLR は AppDomainをリサイクルせずにスレッド自体を終了しようとします。 リソースが後でなく早くクリーンアップされるようにすると、より多くのリソースを使用できるようになり、有効期間の管理が向上します。 エラー コード パス内のファイルへのハンドルを明示的に閉じなかった場合は、 SafeHandle ファイナライザーによってクリーンアップされるまで待機します。次にコードを実行するときに、ファイナライザーがまだ実行されていない場合は、まったく同じファイルへのアクセスが失敗する可能性があります。 このため、クリーンアップ コードが存在し、正しく動作することを確認すると、厳密に必要ない場合でも、エラーからよりクリーンかつ迅速に復旧するのに役立ちます。
コード分析ルール
catch
後のクリーンアップ コードは、finally
ブロック内にある必要があります。 finally ブロックで破棄する呼び出しを行います。
catch
ブロックはスローまたは再スローで終わる必要があります。 多数の例外を取得する可能性があるネットワーク接続を確立できるかどうかを検出するコードなどの例外がありますが、通常の状況で多数の例外をキャッチする必要があるコードは、成功するかどうかを確認するためにコードをテストする必要があることを示す必要があります。
アプリケーション ドメイン間 Process-Wide 変更可能な共有状態を排除するか、制約付き実行リージョンを使用する必要があります
概要で説明したように、アプリケーション ドメイン間のプロセス全体の共有状態を信頼性の高い方法で監視するマネージド コードを記述することは非常に困難な場合があります。 プロセス全体の共有状態は、Win32 コード、CLR 内、またはリモート処理を使用したマネージド コードのいずれかで、アプリケーション ドメイン間で共有されるあらゆる種類のデータ構造です。 変更可能な共有状態はマネージド コードで正しく記述することは非常に困難であり、静的な共有状態は細心の注意を払って行われる場合があります。 プロセス全体またはマシン全体の共有状態がある場合は、制約付き実行リージョン (CER) を使用して、その状態を排除したり、共有状態を保護したりする何らかの方法を見つけます。 識別および修正されていない共有状態のライブラリでは、クリーン AppDomain アンロードが必要なホスト (SQL Server など) がクラッシュする可能性があることに注意してください。
コードで COM オブジェクトを使用する場合は、アプリケーション ドメイン間でその COM オブジェクトを共有しないようにします。
ロックは、プロセス全体またはアプリケーション ドメイン間では機能しません。
これまでは、 Enter と lock ステートメント を使用してグローバル プロセス ロックを作成してきました。 たとえば、非共有アセンブリからのType インスタンス、Thread オブジェクト、インターン文字列、リモート処理を使用してアプリケーション ドメイン間で共有される一部の文字列など、AppDomainアジャイル クラスをロックすると発生します。 これらのロックは、プロセス全体ではなくなりました。 プロセス全体のアプリケーション間ドメイン ロックの存在を特定するには、ロック内のコードが、ディスク上のファイルやデータベースなどの外部の永続化されたリソースを使用しているかどうかを判断します。
保護されたコードが外部リソースを使用する場合、 AppDomain 内でロックを取ると、そのコードが複数のアプリケーション ドメイン間で同時に実行される可能性があるため、問題が発生する可能性があることに注意してください。 これは、1 つのログ ファイルに書き込む場合や、プロセス全体のソケットにバインドするときに問題になる可能性があります。 これらの変更は、マネージド コードを使用して、名前付き Mutex または Semaphore インスタンスを使用する以外に、プロセス グローバル ロックを取得する簡単な方法がないことを意味します。 2 つのアプリケーション ドメインで同時に実行されないコードを作成するか、 Mutex クラスまたは Semaphore クラスを使用します。 既存のコードを変更できない場合は、ファイバー モードで実行すると、同じオペレーティング システム スレッドがミューテックスを取得して解放することを保証できないため、この同期を実現するために Win32 名前付きミューテックスを使用しないでください。 マネージド Mutex クラス、または名前付き ManualResetEvent、 AutoResetEvent、または Semaphore を使用して、アンマネージ コードを使用してロックを同期するのではなく、CLR が認識する方法でコード ロックを同期する必要があります。
Avoid lock(typeof(MyType))
また、すべてのアプリケーション ドメインで共有されるコードのコピーが 1 つだけの共有アセンブリ内のプライベート オブジェクトとパブリック Type オブジェクトにも問題があります。 共有アセンブリの場合、プロセスごとに Type のインスタンスは 1 つだけです。つまり、複数のアプリケーション ドメインがまったく同じ Type インスタンスを共有します。 Type インスタンスにロックを設定すると、AppDomainだけでなく、プロセス全体に影響するロックが適用されます。 ある AppDomain が Type オブジェクトにロックを取ると、そのスレッドは突然中止され、ロックは解除されません。 このロックにより、他のアプリケーション ドメインがデッドロックする可能性があります。
静的メソッドでロックを取得する良い方法は、静的内部同期オブジェクトをコードに追加することです。 これが存在する場合はクラス コンストラクターで初期化できますが、存在しない場合は次のように初期化できます。
private static Object s_InternalSyncObject;
private static Object InternalSyncObject
{
get
{
if (s_InternalSyncObject == null)
{
Object o = new Object();
Interlocked.CompareExchange(
ref s_InternalSyncObject, o, null);
}
return s_InternalSyncObject;
}
}
ロックを取得するときは、 InternalSyncObject
プロパティを使用して、ロックするオブジェクトを取得します。 クラス コンストラクターで内部同期オブジェクトを初期化している場合は、このプロパティを使用する必要はありません。 ダブル チェック ロック初期化コードは、次の例のようになります。
public static MyClass SingletonProperty
{
get
{
if (s_SingletonProperty == null)
{
lock(InternalSyncObject)
{
// Do not use lock(typeof(MyClass))
if (s_SingletonProperty == null)
{
MyClass tmp = new MyClass(…);
// Do all initialization before publishing
s_SingletonProperty = tmp;
}
}
}
return s_SingletonProperty;
}
}
lock(this) に関する注意事項
一般に、パブリックにアクセス可能な個々のオブジェクトをロックすることは許容されます。 ただし、オブジェクトがシングルトン オブジェクトであり、サブシステム全体がデッドロックを引き起こす可能性がある場合は、上記の設計パターンの使用も検討してください。 たとえば、1 つの SecurityManager オブジェクトをロックすると、 AppDomain 内でデッドロックが発生し、 AppDomain 全体が使用できなくなる可能性があります。 この型のパブリックにアクセス可能なオブジェクトをロックしないことをお勧めします。 ただし、個々のコレクションまたは配列をロックしても、通常は問題は発生しません。
コード分析ルール
アプリケーション ドメイン間で使用される可能性がある型や、厳密な ID 感覚を持たない型に対してロックを取らないでください。 Type、MethodInfo、PropertyInfo、String、ValueType、Thread、またはMarshalByRefObjectから派生したオブジェクトに対してEnterを呼び出さないでください。
GC を削除します。KeepAlive 呼び出し
既存のコードの量が多い場合は、 KeepAlive を使用しないか、適切でない場合に使用します。 SafeHandleに変換した後、クラスはファイナライザーを持たないが、オペレーティング システムハンドルを終了するためにSafeHandleに依存していると仮定して、KeepAliveを呼び出す必要はありません。 KeepAliveの呼び出しを保持するパフォーマンス コストはごくわずかですが、KeepAliveの呼び出しは、存在しなくなった可能性のある有効期間の問題を解決するために必要であるか、十分であるという認識により、コードの保守が困難になります。 ただし、COM 相互運用 CLR 呼び出し可能ラッパー (RCW) を使用する場合、 KeepAlive はコードで引き続き必要です。
コード分析ルール
KeepAlive を削除します。
HostProtection 属性を使用する
HostProtectionAttribute (HPA) は、宣言型セキュリティ アクションを使用してホスト保護の要件を決定します。これにより、ホストは、完全に信頼されたコードでも、特定のホストに不適切な特定のメソッド (SQL Server のExitやShowなど) を呼び出さないようにすることができます。
HPA は、共通言語ランタイムをホストし、SQL Server などのホスト保護を実装するアンマネージド アプリケーションにのみ影響します。 セキュリティ アクションを適用すると、クラスまたはメソッドが公開するホスト リソースに基づいてリンク要求が作成されます。 コードがクライアント アプリケーションまたはホストで保護されていないサーバーで実行されている場合、属性は "蒸発" します。検出されないため、適用されません。
Von Bedeutung
この属性の目的は、セキュリティ動作ではなく、ホスト固有のプログラミング モデル ガイドラインを適用することです。 リンク要求はプログラミング モデルの要件への準拠を確認するために使用されますが、 HostProtectionAttribute はセキュリティアクセス許可ではありません。
ホストにプログラミング モデルの要件がない場合、リンクの要求は発生しません。
この属性は、次を識別します。
ホスト プログラミング モデルには適合しないが、それ以外の場合は無害なメソッドまたはクラス。
ホスト プログラミング モデルに適合せず、サーバーマネージド ユーザー コードが不安定になる可能性があるメソッドまたはクラス。
ホスト プログラミング モデルに適合せず、サーバー プロセス自体が不安定になる可能性があるメソッドまたはクラス。
注
ホストで保護された環境で実行される可能性のあるアプリケーションによって呼び出されるクラス ライブラリを作成する場合は、リソース カテゴリ HostProtectionResource 公開するメンバーにこの属性を適用する必要があります。 この属性を持つ .NET Framework クラス ライブラリ のメンバーにより、直ちに呼び出し元のみがチェックされます。 ライブラリ メンバーも、同じ方法で直ちに呼び出し元を確認する必要があります。
HPA の詳細については、 HostProtectionAttributeを参照してください。
コード分析ルール
SQL Server の場合、同期またはスレッド化を導入するために使用されるすべてのメソッドを HPA で識別する必要があります。 これには、状態を共有したり、同期したり、外部プロセスを管理したりするメソッドが含まれます。 SQL Server に影響を与える HostProtectionResource 値は、 SharedState、 Synchronization、および ExternalProcessMgmtです。 ただし、すべての HostProtectionResource を公開するメソッドは、SQL に影響を与えるリソースを使用しているメソッドだけでなく、HPA によって識別される必要があります。
アンマネージ コードで無期限にブロックしない
マネージド コードではなくアンマネージ コードでブロックすると、CLR がスレッドを中止できないため、サービス拒否攻撃が発生する可能性があります。 ブロックされたスレッドは、CLR が AppDomainをアンロードするのを防ぎます。少なくとも、非常に安全でない操作を行う必要はありません。 Windows 同期プリミティブを使用したブロックは、許可できないことの明確な例です。 ソケットで ReadFile
を呼び出す際のブロックは、可能であれば回避する必要があります。理想的には、Windows API は、このような操作をタイムアウトするためのメカニズムを提供する必要があります。
ネイティブを呼び出すメソッドは、理想的には、妥当な有限タイムアウトで Win32 呼び出しを使用する必要があります。 ユーザーがタイムアウトを指定できる場合、ユーザーは、特定のセキュリティアクセス許可なしで無限タイムアウトを指定することはできません。 ガイドラインとして、メソッドが最大 10 秒を超えてブロックされる場合は、タイムアウトをサポートするバージョンを使用する必要があります。または、追加の CLR サポートが必要です。
問題のある API の例をいくつか次に示します。 パイプ (匿名と名前付きの両方) はタイムアウトで作成できます。ただし、コードは、NMPWAIT_WAIT_FOREVERで CreateNamedPipe
を呼び出したり WaitNamedPipe
したりしないようにする必要があります。 さらに、タイムアウトが指定されている場合でも、予期しないブロックが発生する可能性があります。 匿名パイプで WriteFile
を呼び出すと、すべてのバイトが書き込まれるまでブロックされます。つまり、バッファーに未読データがある場合、 WriteFile
呼び出しは、リーダーがパイプのバッファー内の領域を解放するまでブロックされます。 ソケットでは、タイムアウト メカニズムを受け入れた API を常に使用する必要があります。
コード分析ルール
アンマネージド コードでタイムアウトなしでブロックすることは、サービス拒否攻撃です。
WaitForSingleObject
、WaitForSingleObjectEx
、WaitForMultipleObjects
、MsgWaitForMultipleObjects
、およびMsgWaitForMultipleObjectsEx
に対してプラットフォーム呼び出しを実行しないでください。 NMPWAIT_WAIT_FOREVERは使用しないでください。
STA-Dependent 機能を特定する
COM シングル スレッド アパートメント (STA) を使用するコードを特定します。 SQL Server プロセスでは、STA は無効になります。 パフォーマンス カウンターやクリップボードなど、 CoInitialize
に依存する機能は、SQL Server 内で無効にする必要があります。
ファイナライザーに同期の問題がないことを確認する
.NET Framework の将来のバージョンには、複数のファイナライザー スレッドが存在する可能性があります。つまり、同じ型の異なるインスタンスのファイナライザーが同時に実行されます。 完全にスレッド セーフである必要はありません。ガベージ コレクターは、特定のオブジェクト インスタンスに対してファイナライザーを実行するスレッドが 1 つだけであることを保証します。 ただし、複数の異なるオブジェクト インスタンスで同時に実行する場合は、競合状態やデッドロックを回避するためにファイナライザーをコーディングする必要があります。 ファイナライザーでログ ファイルへの書き込みなどの外部状態を使用する場合は、スレッドの問題を処理する必要があります。 スレッド セーフを提供するために最終処理に依存しないでください。 ファイナライザー スレッドに状態を格納するために、マネージドまたはネイティブのスレッド ローカル ストレージを使用しないでください。
コード分析ルール
ファイナライザーは、同期の問題を含まない必要があります。 ファイナライザーで静的変更可能な状態を使用しないでください。
可能な場合はアンマネージ メモリを避ける
アンマネージド メモリは、オペレーティング システム ハンドルと同様にリークされる可能性があります。 可能であれば、 stackalloc または固定されたマネージド オブジェクト ( 固定ステートメント 、byte[] を使用した GCHandle など) を使用して、スタック上のメモリを使用してみてください。 GCは最終的にこれらをクリーンアップします。 ただし、アンマネージ メモリを割り当てる必要がある場合は、 SafeHandle から派生したクラスを使用してメモリ割り当てをラップすることを検討してください。
SafeHandleが十分でないケースが少なくとも 1 つあります。 メモリの割り当てまたは解放を行う COM メソッド呼び出しでは、1 つの DLL が CoTaskMemAlloc
を介してメモリを割り当てるのが一般的であり、別の DLL は CoTaskMemFree
でそのメモリを解放します。 これらの場所で SafeHandle を使用すると、他の DLL がメモリの有効期間を制御するのではなく、アンマネージ メモリの有効期間を SafeHandle の有効期間に関連付けようとするため、不適切です。
catch(Exception) のすべての使用を確認する
1 つの特定の例外ではなく、すべての例外をキャッチするブロックをキャッチすると、非同期例外もキャッチされるようになりました。 すべての catch(Exception) ブロックを調べて、スキップされる可能性のある重要なリソース解放コードやバックアウト コードを探し、 ThreadAbortException、 StackOverflowException、または OutOfMemoryExceptionを処理するために catch ブロック自体内で誤った動作が発生する可能性があることを確認します。 このコードは、特定の例外のみが表示される可能性がある、または例外が発生するたびに 1 つの特定の理由で失敗したという前提をログに記録または行っている可能性があることに注意してください。 これらの前提条件は、 ThreadAbortExceptionを含めるために更新する必要がある場合があります。
すべての例外をキャッチするすべての場所を変更して、文字列の書式設定メソッドからの FormatException など、スローされる特定の種類の例外をキャッチすることを検討してください。 これにより、catch ブロックが予期しない例外で実行されるのを防ぎ、予期しない例外をキャッチしてコードがバグを隠さないようにするのに役立ちます。 一般的なルールとして、ライブラリ コードで例外を処理することはありません (例外をキャッチする必要があるコードは、呼び出しているコードの設計上の欠陥を示している可能性があります)。 場合によっては、例外をキャッチし、別の例外の種類をスローして、より多くのデータを提供したい場合があります。 この場合は、入れ子になった例外を使用して、エラーの実際の原因を新しい例外の InnerException プロパティに格納します。
コード分析ルール
すべてのオブジェクトをキャッチするか、すべての例外をキャッチするマネージド コード内のすべての catch ブロックを確認します。 C# では、これは catch
{} と catch(Exception)
{}の両方にフラグを設定します。 例外の種類を非常に具体的にすることを検討するか、予期しない例外の種類をキャッチした場合に不適切な方法で動作しないようにコードを確認してください。
マネージド スレッドが Win32 スレッドであると想定しないでください。
マネージド スレッド ローカル ストレージの使用は機能しますが、アンマネージド スレッド ローカル ストレージを使用しない場合や、コードが現在のオペレーティング システム スレッドで再度実行されると想定する場合があります。 スレッドのロケールなどの設定は変更しないでください。 ロックに入ったオペレーティング システム スレッドもロックを終了する必要があるため、プラットフォーム呼び出しを介して InitializeCriticalSection
または CreateMutex
を呼び出さないでください。 ファイバーを使用する場合はこれが当たらないため、Win32 クリティカル セクションとミューテックスを SQL で直接使用することはできません。 マネージド Mutex クラスでは、これらのスレッド アフィニティの問題は処理されないことに注意してください。
マネージド スレッド ローカル ストレージやスレッドの現在の UI カルチャなど、マネージド Thread オブジェクトの状態のほとんどを安全に使用できます。 ThreadStaticAttributeを使用することもできます。これにより、既存の静的変数の値に現在のマネージド スレッドのみがアクセスできるようになります (これは CLR でファイバー ローカル ストレージを実行するもう 1 つの方法です)。 プログラミング モデルの理由から、SQL で実行するときにスレッドの現在のカルチャを変更することはできません。
コード分析ルール
SQL Server はファイバー モードで実行されます。スレッド ローカル ストレージは使用しないでください。
TlsAlloc
、TlsFree
、TlsGetValue
、およびTlsSetValue.
SQL Server で偽装を処理できるようにする
偽装はスレッド レベルで動作し、SQL はファイバー モードで実行できるため、マネージド コードはユーザーを偽装せず、 RevertToSelf
を呼び出すべきではありません。
コード分析ルール
SQL Server が偽装を処理できるようにします。
RevertToSelf
、ImpersonateAnonymousToken
、DdeImpersonateClient
、ImpersonateDdeClientWindow
、ImpersonateLoggedOnUser
、ImpersonateNamedPipeClient
、ImpersonateSelf
、RpcImpersonateClient
、RpcRevertToSelf
、RpcRevertToSelfEx
、またはSetThreadToken
は使用しないでください。
Thread::Suspend を呼び出さない
スレッドを中断する機能は単純な操作のように見えるかもしれませんが、デッドロックが発生する可能性があります。 ロックを保持しているスレッドが 2 番目のスレッドによって中断された後、2 番目のスレッドが同じロックを取得しようとすると、デッドロックが発生します。 Suspend は、現在、セキュリティ、クラスの読み込み、リモート処理、およびリフレクションを妨げる可能性があります。
コード分析ルール
Suspend を呼び出さないでください。 SemaphoreやManualResetEventなど、実際の同期プリミティブを代わりに使用することを検討してください。
制約付き実行領域と信頼性コントラクトを使用して重要な操作を保護する
共有状態を更新する複雑な操作を実行する場合、または完全に成功するか完全に失敗するかを決定論的に行う必要がある場合は、制約付き実行リージョン (CER) によって保護されていることを確認してください。 これにより、突然のスレッドの中止や突然の AppDomain アンロードであっても、すべてのケースでコードが実行されます。
CER は、PrepareConstrainedRegionsの呼び出しの直前にある特定のtry/finally
ブロックです。
これにより、just-in-time コンパイラは、 try
ブロックを実行する前に finally ブロック内のすべてのコードを準備するように指示します。 これにより、finally ブロック内のコードがビルドされ、すべてのケースで実行されます。 CER で空の try
ブロックを使用することは珍しくありません。 CER を使用すると、非同期スレッドの中止やメモリ不足の例外から保護されます。 非常に深いコードのスタック オーバーフローを処理する CER の形式については、 ExecuteCodeWithGuaranteedCleanup を参照してください。
こちらも参照ください
.NET