4

いくつかのスレッドコードを書く際に、私はReaderWriterLockSlim変数への同期アクセスを処理するためにクラスを使用してきました。これを行うと、メソッドとプロパティごとに同じ、try-finallyブロックを常に作成していることに気付きました。

繰り返しを避けてこの動作をカプセル化する機会を見つけて、C#ブロック構文ReaderWriterLockSectionで使用できるロックの薄いラッパーとして使用することを目的としたクラスを作成しました。using

クラスは主に次のとおりです。

public enum ReaderWriterLockType
{
   Read,
   UpgradeableRead,
   Write
}

public class ReaderWriterLockSection : IDisposeable
{
   public ReaderWriterLockSection(
       ReaderWriterLockSlim lock, 
       ReaderWriterLockType lockType)
   {
       // Enter lock.
   }

   public void UpgradeToWriteLock()
   {
       // Check lock can be upgraded.
       // Enter write lock.
   }

   public void Dispose()
   {
       // Exit lock.
   }
}

私は次のようにセクションを使用します:

private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

public void Foo()
{
    using(new ReaderWriterLockSection(_lock, ReaderWriterLockType.Read)
    {
        // Do some reads.
    }
}

私には、これは良い考えのように思えます。ロックを解除することを決して忘れないので、コードを読みやすくし、一見より堅牢に見えるようにするものです。

誰かがこのアプローチの問題を見ることができますか?これが悪い考えである理由はありますか?

4

4 に答える 4

3

まあ、それは私には大丈夫のようです。Eric Lippertは以前、「非リソース」シナリオに使用することの危険性について書いていDisposeますが、これはリソースとしてカウントされると思います。

アップグレードシナリオでは作業が難しくなる可能性がありますが、その時点でいつでもより手動のコードにフォールバックできます。

もう1つの方法は、単一のロック取得/使用/解放メソッドを記述し、委任としてロックを保持している間に実行するアクションを提供することです。

于 2009-12-14T11:06:58.277 に答える
1

「使用」パターンを容易にするためにロックをラップするときに提案することの1つは、ロックに「危険状態」フィールドを含めることです。コードがロックに入るのを許可する前に、コードは危険状態をチェックする必要があります。危険状態が設定されていて、ロックに入ろうとしているコードが、ロックを取得しようとしている可能性があることを示す特別なパラメーターを渡していない場合、ロックを取得しようとすると例外がスローされます。保護されたリソースを一時的に不良状態にするコードは、危険状態フラグを設定し、必要な処理を実行し、操作が完了してオブジェクトが安全状態になったら危険状態フラグをリセットする必要があります。

危険状態フラグが設定されているときに例外が発生した場合は、ロックを解除する必要がありますが、危険状態フラグは設定されたままにする必要があります。これにより、リソースにアクセスしたいコードは、ロックが解放されるのを永久に待つのではなく、リソースが破損していることを検出します(これは、「using」または「try-finally」ブロックがなかった場合の結果になります) )。

ラップされているロックがReaderWriterLockの場合、「ライター」ロックを取得すると、危険状態が自動的に設定されると便利な場合があります。残念ながら、ブロックによって使用されるIDisposableが、usingブロックが正常に終了しているか、例外によって終了しているかを判断する方法はありません。したがって、「危険状態」フラグを保護するために「using」ブロックのような構文的に何かを使用する方法はわかりません。

于 2012-03-04T00:04:36.710 に答える
1

私は通常、この種のコード糖質の菓子にふける!

APIに加えて、ユーザーにとって少し読みやすいバリアントを次に示します。

public static class ReaderWriterLockExt{
  public static IDisposable ForRead(ReaderWriterLockSlim rwLock){
    return new ReaderWriterLockSection(rwLock,ReaderWriterLockType.Read);
  }
  public static IDisposable ForWrite(ReaderWriterLockSlim rwLock){
    return new ReaderWriterLockSection(rwLock,ReaderWriterLockType.Write);
  }
  public static IDisposable ForUpgradeableRead(ReaderWriterLockSlim wrLock){
    return new ReaderWriterLockSection(rwLock,ReaderWriterLockType.UpgradeableRead);
  }
}

public static class Foo(){
  private static readonly ReaderWriterLockSlim l=new ReaderWriterLockSlim(); // our lock
  public static void Demo(){


    using(l.ForUpgradeableRead()){ // we might need to write..

      if(CacheExpires()){   // checks the scenario where we need to write

         using(l.ForWrite()){ // will request the write permission
           RefreshCache();
         } // relinquish the upgraded write
      }

      // back into read mode
      return CachedValue();
    } // release the read 
  }
}

また、ロックを10秒間取得できない場合に呼び出されるアクションデリゲートを取得するバリアントを使用することをお勧めします。これは、読者の演習として残しておきます。

また、静的拡張メソッドでnull RWLをチェックし、破棄するときにロックが存在することを確認することもできます。

乾杯、フロリアン

于 2009-12-14T11:39:37.913 に答える
1

ここには別の考慮事項があります。解決すべきではない問題を解決している可能性があります。私はあなたのコードの残りを見ることができませんが、私はあなたからこのパターンの価値を見ていると推測することができます。

あなたのアプローチは、共有リソースを読み書きするコードが例外をスローした場合にのみ問題を解決します。暗黙的には、読み取り/書き込みを行うメソッドで例外を処理しないことです。そうした場合は、例外処理コードでロックを解除するだけで済みます。例外をまったく処理しない場合、スレッドは未処理の例外で終了し、プログラムは終了します。その場合、ロックを解除しても意味がありません。

したがって、例外をキャッチして処理するcatch句がコールスタックのどこかにあります。このコードは、プログラムの状態を復元して、悪いデータを生成したり、状態の変更によって引き起こされた例外が原因で停止したりすることなく、意味のある継続ができるようにする必要があります。そのコードは実行するのが難しい仕事をしています。コンテキストがなくても、どのくらいのデータが読み取られたか、または書き込まれたかを何らかの方法で推測する必要があります。それを間違えるか、部分的に正しくすると、プログラム全体が非常に不安定になる可能性があります。結局のところ、それは共有リソースであり、他のスレッドがそこから読み取り/書き込みを行っています。

これを行う方法を知っている場合は、必ずこのパターンを使用してください。しかし、あなたは一体をテストする方が良いです。よくわからない場合は、確実に修正できない問題でシステムリソースを浪費しないようにしてください。

于 2009-12-14T13:41:33.223 に答える