1

理解するのを手伝ってください。私はそれを読みました

「ファイナライザーの実行の時間と順序を予測または事前に決定することはできません」

正しい?

ただし、RavenDB ソース コード TransactionStorage.cs を見ると、これがわかります

~TransactionalStorage()
{
try
{
 Trace.WriteLine(
  "Disposing esent resources from finalizer! You should call TransactionalStorage.Dispose() instead!");
 Api.JetTerm2(instance, TermGrbit.Abrupt);
}
catch (Exception exception)
{
  try
  {
   Trace.WriteLine("Failed to dispose esent instance from finalizer because: " + exception);
   }
   catch
   {
   }
 }
}

おそらく SafeHandle を使用してネイティブ リソースのハンドルを取得する API クラス (Managed Esent に属する)?

したがって、ネイティブ ハンドルの SafeHandle が TransactionStorage の前にファイナライズされる可能性があることを正しく理解していれば、望ましくない影響を与える可能性があります。

実際に Esent コードに飛び込むと、SafeHandles は使用されません。

C#経由のCLRによると、これは危険ですか?

    internal static class SomeType {  
   [DllImport("Kernel32", CharSet=CharSet.Unicode, EntryPoint="CreateEvent")]  

 // This prototype is not robust  
   private static extern IntPtr CreateEventBad( 
      IntPtr pSecurityAttributes, Boolean manualReset, Boolean initialState, String name);  


 // This prototype is robust  
  [DllImport("Kernel32", CharSet=CharSet.Unicode, EntryPoint="CreateEvent")]  
  private static extern SafeWaitHandle CreateEventGood( 
     IntPtr pSecurityAttributes, Boolean manualReset, Boolean initialState, String name)

  public static void SomeMethod() {  
     IntPtr         handle = CreateEventBad(IntPtr.Zero, false, false, null);  
     SafeWaitHandle swh    = CreateEventGood(IntPtr.Zero, false, false, null);  
  }  
}

Managed Esent (NativeMEthods.cs) は次のようになります (Ints と IntPtrs を使用しますか?):

  [DllImport(EsentDll, CharSet = EsentCharSet, ExactSpelling = true)]
    public static extern int JetCreateDatabase(IntPtr sesid, string szFilename, string szConnect, out uint dbid, uint grbit);

Managed Esent はファイナライズ/破棄を正しい方法で処理していますか? 2 つ目は、RavenDB がファイナライザーを正しい方法で処理しているか、Managed Esent を補っていますか?

4

3 に答える 3

7

ESENT リソースで SafeHandles を使用するのは非常に複雑で危険なので、使用しないことにしました。次の 2 つの大きな問題があります。

  1. Win32 ハンドルとは異なり、ESENT ハンドルは相互に関連しているため、1 つのハンドルを閉じると他のハンドルも暗黙的に閉じられます。
  2. 既に閉じられている ESENT ハンドルを閉じるのは安全ではありません。

最初の点については、考慮すべきいくつかのケースがあります。

  • JetRollback はトランザクション内で開いているすべてのテーブルを閉じますが、JetCommit は閉じません。
  • JetEndSession は、セッションによって開かれたすべてのテーブルとデータベースを閉じます。
  • JetTerm は、インスタンスによって開かれたすべてのセッション、テーブル、およびデータベースを閉じることができます。

現在、JET_SESID または JET_TABLEID は、実際には内部構造へのポインターです (ハンドル テーブルを介して間接化しようとしましたが、特に複数のスレッドで使用される場合は遅すぎることがわかりました)。つまり、リソースが閉じられると、メモリを再利用できます。リソースを再度解放すると、別のスレッドのリソースが解放される可能性があります。ポインタを二重に解放するのと同じです。

そのため、ファイナライズに関しては、このコードは驚くほど困難になります。

void Foo(JET_SESID sesid, JET_DBID dbid)
{
    JET_TABLEID tableid;

    Api.JetBeginTransaction(sesid);
    Api.JetOpenTable(sesid, dbid, "table", null, 0, OpenTableGrbit.None, out tableid);
    // do something...
    if (somethingFailed)
    {
        Api.JetRollback(sesid, RollbackTransactionGrbit.None);
    }
    else
    {
        Api.JetCommitTransaction(sesid, CommitTransactionGrbit.None);
    }
}

JET_TABLEID が SafeHandle にラップされている場合、JetRollback() 呼び出し (パラメーターとして tableid を使用することさえありません) がテーブルを閉じたことを知る必要があるため、ファイナライザーはテーブルを閉じることができません。一方、コミット パスを使用する場合、ファイナライザーはテーブルを閉じる必要があります。

JET_SESID も SafeHandle である場合、ファイナライザーが実行される順序を追跡する必要があります。JET_SESID が既にファイナライズされている場合、JET_TABLEID を閉じることはできません。

インスタンス、セッション、テーブル、およびトランザクション間の関係を追跡し、ファイナライザーで正しいことを行うのは非常に困難であり、ManagedEsent が提供するよりも洗練されたオブジェクト モデルを使用して行うのが最適です。

ただし、インスタンスを暗黙的に閉じることができる API がないため、JET_INSTANCE で SafeHandle を使用できます。Instance() ラッパーがそれを行います。JET_INSTANCE を SafeHandle にしないのはなぜですか? ESENT をまったく終了せずにアプリケーションを終了したい場合があります。終了は遅くなる可能性があり、永続的なトランザクションでは、プログラムを終了するだけで実際に情報が失われることはありません。JetInit でデータベースの回復が自動的に実行されます。

ファイナライザーの順序に関しては、重要なファイナライザー (SafeHandles など) は常に、すべての通常のファイナライザーが実行された後に実行されると思います。

于 2010-05-21T18:12:30.210 に答える
2

ここには多くの問題があります。私のコメント:

  1. ファイナライザーは、ファイナライザーが他のすべてのクラスですでに実行されていることを前提としている必要があります。Trace.WriteLineこれには、たとえば、ステートメントをログファイルに書き出すクラスが含まれます。
  2. catch句は、すでにファイナライズされたクラスから保護するために機能する場合と機能しない場合があります。通常、ファイナライザーは、管理されていないリソースの解放に失敗した場合でもスローしません(通常、プログラム全体がクラッシュするため)。
  3. ここで説明する理由から、マネージドESENTは確かにsを使用する必要があります(つまり、非同期例外が発生した場合のリークの防止と、ハンドルのリサイクルからの保護)。Laurionがここでベストプラクティスを使用していないことに本当に驚いています。SafeHandle
  4. ESENTセッションIDはIntPtrsですが、それらのDBIDはuintsです。ただし、どちらもSafeHandle派生クラスでラップする必要があります。

つまり、両方のプロジェクトがファイナライズを処理していないか、正しく処理していないようです。私の最初のCodeProjectの記事が指摘しているように、これは難しい問題です。

マネージドESENTがリリースされる前は、OSSとしてリリースすることを目的として、ESENTAPIの独自のラッパーの作業を開始していました。Laurionと何度か話し合った後、Microsoftもそうするつもりだったので、そうしないことにしました。私が持っているコードは機能が完全ではありませんが、SafeHandle興味があれば、sを適切に使用します。

また、ESENTには書き込み時に下位互換性がないことに注意してください(強制的にアップグレードされるため、Win7で開いたXPのデータベースをXPで再度読み取ることはできません)。すべてのデータがマシンに対してローカルである場合は適切な選択ですが、データベースを他のマシンにコピーする必要がある場合は使用できません。

于 2010-05-21T09:43:43.253 に答える
1

SafeHandlesについて明確にしたいと思います。SafeHandlesは、ファイナライズ可能なオブジェクトであるだけでなく、クリティカルファイナライズと呼ばれるファイナライズのより強力な保証があります。クリティカルファイナライザーは、すべての通常のファイナライザーがすでに実行された後に実行されることが保証されます。このため、SafeHandlesが最後に完成することが保証されています。

重要なファイナライズは、SafeHandleを安全にする機能の1つです:)

このリンクには詳細が含まれています。お役に立てれば。

于 2010-05-23T06:53:22.163 に答える