85

読み取り/書き込みロックを使用するプロパティがたくさんあります。try finallyまたはusing句のいずれかでそれらを実装できます。

では のtry finally前にロックを取得しtry、 で解放しfinallyます。このusing節では、コンストラクターでロックを取得し、Dispose メソッドで解放するクラスを作成します。

私は多くの場所で読み取り/書き込みロックを使用しているので、より簡潔な方法を探していましたtry finally。ある方法が推奨されない理由、またはある方法が別の方法よりも優れている理由について、いくつかのアイデアを聞きたいと思っています。

方法 1 ( try finally):

static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
    get
    {
        rwlMyLock_m .AcquireReaderLock(0);
        try
        {
            return dtMyDateTime_m
        }
        finally
        {
            rwlMyLock_m .ReleaseReaderLock();
        }
    }
    set
    {
        rwlMyLock_m .AcquireWriterLock(0);
        try
        {
            dtMyDateTime_m = value;
        }
        finally
        {
            rwlMyLock_m .ReleaseWriterLock();
        }
    }
}

方法 2:

static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
    get
    {
        using (new ReadLock(rwlMyLock_m))
        {
            return dtMyDateTime_m;
        }
    }
    set
    {
        using (new WriteLock(rwlMyLock_m))
        {
            dtMyDateTime_m = value;
        }
    }
}

public class ReadLock : IDisposable
{
    private ReaderWriterLock rwl;
    public ReadLock(ReaderWriterLock rwl)
    {
        this.rwl = rwl;
        rwl.AcquireReaderLock(0);
    }

    public void Dispose()
    {
        rwl.ReleaseReaderLock();
    }
}

public class WriteLock : IDisposable
{
    private ReaderWriterLock rwl;
    public WriteLock(ReaderWriterLock rwl)
    {
        this.rwl = rwl;
        rwl.AcquireWriterLock(0);
    }

    public void Dispose()
    {
        rwl.ReleaseWriterLock();
    }
}
4

15 に答える 15

97

MSDN から、Statement を使用して (C# リファレンス)

using ステートメントを使用すると、オブジェクトのメソッドを呼び出しているときに例外が発生した場合でも、Dispose が確実に呼び出されます。オブジェクトを try ブロック内に配置してから、finally ブロック内で Dispose を呼び出すことによって、同じ結果を得ることができます。実際、これは using ステートメントがコンパイラによってどのように変換されるかです。前のコード例は、コンパイル時に次のコードに展開されます (オブジェクトの制限されたスコープを作成するために追加の中かっこに注意してください)。

{
  Font font1 = new Font("Arial", 10.0f);
  try
  {
    byte charset = font1.GdiCharSet;
  }
  finally
  {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}

したがって、基本的には同じコードですが、優れた自動 null チェックと variable の追加スコープがあります。ドキュメントには、「IDisposable オブジェクトの正しい使用を保証する」とも記載されているため、将来、あいまいなケースに対してさらに優れたフレームワーク サポートが得られる可能性があります。

したがって、オプション 2 を使用します。

変数が不要になった直後に終了するスコープ内に変数があることもプラスです。

于 2008-11-10T19:43:02.067 に答える
12

私は間違いなく2番目の方法を好みます。使用する時点でより簡潔になり、エラーが発生しにくくなります。

最初のケースでは、コードを編集する人は、Acquire(Read|Write)Lock 呼び出しと try の間に何も挿入しないように注意する必要があります。

(ただし、このように個々のプロパティに読み取り/書き込みロックを使用するのは、通常はやり過ぎです。それらは、はるかに高いレベルで適用するのが最適です。ロックが保持されている時間を考えると、競合の可能性はおそらく非常に小さいため、ここでは単純なロックで十分です。読み取り/書き込みロックの取得は、単純なロックよりも高価な操作です)。

于 2008-11-10T19:36:22.797 に答える
9

例外をマスクするため、両方のソリューションが悪い可能性があることを考慮してください。

atryなしの acatchは明らかに悪い考えです。このステートメントが同様に危険である理由については、MSDNを参照してください。using

また、Microsoft は現在、ReaderWriterLock の代わりに ReaderWriterLockSlim を推奨してます。

最後に、Microsoft の例では、これらの問題を回避するために2 つの try-catch ブロックを使用していることに注意してください。

try
{
    try
    {
         //Reader-writer lock stuff
    }
    finally
    {
         //Release lock
    }
 }
 catch(Exception ex)
 {
    //Do something with exception
 }

シンプルで一貫性のあるクリーンなソリューションは良い目標ですが、単に を使用できないと仮定するとlock(this){return mydateetc;}、アプローチを再考することができます。詳細については、スタック オーバーフローが役立つと確信しています ;-)

于 2008-11-10T19:52:04.730 に答える
5

私は個人的に C# の "using" ステートメントを可能な限り頻繁に使用していますが、前述の潜在的な問題を回避するために、いくつかの特定のことを実行しています。説明する:

void doSomething()
{
    using (CustomResource aResource = new CustomResource())
    {
        using (CustomThingy aThingy = new CustomThingy(aResource))
        {
            doSomething(aThingy);
        }
    }
}

void doSomething(CustomThingy theThingy)
{
    try
    {
        // play with theThingy, which might result in exceptions
    }
    catch (SomeException aException)
    {
        // resolve aException somehow
    }
}

「using」ステートメントを 1 つのメソッドに分離し、オブジェクトの使用を「try」/「catch」ブロックを使用して別のメソッドに分離していることに注意してください。関連するオブジェクトに対して、このような「using」ステートメントをいくつか入れ子にすることがあります (実動コードで 3 つまたは 4 つの深さになることがあります)。

Dispose()これらのカスタムクラスのメソッドでは、IDisposable例外をキャッチし (エラーは除く)、ログに記録します (Log4net を使用)。これらの例外のいずれかが私の処理に影響を与える可能性がある状況に遭遇したことはありません. 潜在的なエラーは、通常どおり、コール スタックを伝播し、通常は適切なメッセージ (エラーとスタック トレース) をログに記録して処理を終了することができます。

途中で重大な例外が発生する可能性がある状況に何らかの形で遭遇した場合、Dispose()その状況に合わせて再設計します。率直に言って、それが起こるとは思えません。

一方、「使用」のスコープとクリーンアップの利点により、これは私の最もお気に入りの C# 機能の 1 つになっています。ところで、私は主要な言語として Java、C#、および Python を使用しており、他にも多くの言語があちこちに散りばめられています。「使用」は、実用的で日常的な主力製品であるため、私の最もお気に入りの言語機能の 1 つです。 .

于 2008-11-10T20:40:18.143 に答える
4

私は3番目のオプションが好きです

private object _myDateTimeLock = new object();
private DateTime _myDateTime;

public DateTime MyDateTime{
  get{
    lock(_myDateTimeLock){return _myDateTime;}
  }
  set{
    lock(_myDateTimeLock){_myDateTime = value;}
  }
}

2 つのオプションのうち、2 番目のオプションが最もクリーンで、何が起こっているのかを理解しやすいものです。

于 2008-11-10T19:43:34.280 に答える
4

DRYは次のように述べています。2 番目の解決策です。最初のソリューションはロックを使用するロジックを複製しますが、2 番目のソリューションは複製しません。

于 2008-11-10T19:42:06.450 に答える
4

「プロパティの束」と、プロパティの getter および setter レベルでのロックは間違っているように見えます。あなたのロックは細かすぎます。最も一般的なオブジェクトの使用法では、同時に複数のプロパティアクセスするためにロックを取得したことを確認する必要があります。あなたの特定のケースは異なるかもしれませんが、私はそれを疑っています。

とにかく、プロパティではなくオブジェクトにアクセスするときにロックを取得すると、記述する必要があるロック コードの量が大幅に削減されます。

于 2008-11-10T21:32:14.310 に答える
1

Try/Catch ブロックは一般に例外処理用であり、using ブロックはオブジェクトが確実に破棄されるようにするために使用されます。

読み取り/書き込みロックの場合、try/catch が最も便利ですが、次のように両方を使用することもできます。

using (obj)
{
  try { }
  catch { }
}

IDisposable インターフェイスを暗黙的に呼び出し、例外処理を簡潔にすることができます。

于 2008-11-10T19:43:13.220 に答える
0

try-finallyを無名関数にカプセル化することを提案した人がいないことに驚いています。usingステートメントを使用してクラスをインスタンス化および破棄する手法と同様に、これによりロックが1か所に保持されます。ロックを解除することを考えているときは、「破棄」という言葉よりも「最後に」という言葉を読みたいという理由だけで、これを自分で好む。

class StackOTest
{
    private delegate DateTime ReadLockMethod();
    private delegate void WriteLockMethod();

    static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
    private DateTime dtMyDateTime_m;
    public DateTime MyDateTime
    {
        get
        {
            return ReadLockedMethod(
                rwlMyLock_m,
                delegate () { return dtMyDateTime_m; }
            );
        }
        set
        {
            WriteLockedMethod(
                rwlMyLock_m,
                delegate () { dtMyDateTime_m = value; }
            );
        }
    }

    private static DateTime ReadLockedMethod(
        ReaderWriterLock rwl,
        ReadLockMethod method
    )
    {
        rwl.AcquireReaderLock(0);
        try
        {
            return method();
        }
        finally
        {
            rwl.ReleaseReaderLock();
        }
    }

    private static void WriteLockedMethod(
        ReaderWriterLock rwl,
        WriteLockMethod method
    )
    {
        rwl.AcquireWriterLock(0);
        try
        {
            method();
        }
        finally
        {
            rwl.ReleaseWriterLock();
        }
    }
}
于 2009-05-31T19:22:57.027 に答える
0

ロックの粒度や疑わしい例外処理など、上記のコメントの多くに同意しますが、問題はアプローチの 1 つです。私が try {} finally model... 抽象化よりも使用することを好む大きな理由を 1 つ挙げましょう。

1 つの例外を除いて、私はあなたのモデルと非常によく似たモデルを持っています。基本インターフェイス ILock を定義し、その中で Acquire() というメソッドを 1 つ提供しました。Acquire() メソッドは IDisposable オブジェクトを返しました。その結果、対象のオブジェクトが ILock 型である限り、ロック スコープを実行するために使用できることを意味します。何でこれが大切ですか?

私たちは、さまざまなロックメカニズムと動作を扱います。ロック オブジェクトには、使用する特定のタイムアウトがある場合があります。ロックの実装は、モニター ロック、リーダー ロック、ライター ロック、またはスピン ロックの場合があります。ただし、呼び出し元の観点からは、それらはすべて無関係です。彼らが気にかけているのは、リソースをロックする契約が尊重され、ロックがその実装と一致する方法でそれを行うことです。

interface ILock {
    IDisposable Acquire();
}

class MonitorLock : ILock {
    IDisposable Acquire() { ... acquire the lock for real ... }
}

私はあなたのモデルが好きですが、呼び出し元からロックの仕組みを隠すことを検討します. FWIW、私は using テクニックと try-finally のオーバーヘッドを測定しました。使い捨てオブジェクトを割り当てるオーバーヘッドは、2 ~ 3% のパフォーマンス オーバーヘッドになります。

于 2008-11-11T05:04:19.203 に答える
0

実際、最初の例では、ソリューションを比較可能にするために、IDisposableそこにも実装します。次に、ロックを直接解放する代わりにDispose()、ブロックから呼び出します。finally

次に、「リンゴからリンゴへ」の実装 (および MSIL) になります (MSIL は両方のソリューションで同じになります)。using追加されたスコープと、フレームワークが の適切な使用を保証するため、おそらく使用することをお勧めしますIDisposable(後者は、自分で実装している場合はあまり有益ではありませんIDisposable)。

于 2012-03-26T03:49:42.030 に答える
0

SoftwareJedi さん、アカウントを持っていないので、回答を編集できません。

いずれにせよ、読み取りロックには常に戻り値が必要だったので、以前のバージョンは一般的な用途には向いていませんでした。これにより、次のことが修正されます。

class StackOTest
{
    static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
    private DateTime dtMyDateTime_m;
    public DateTime MyDateTime
    {
        get
        {
            DateTime retval = default(DateTime);
            ReadLockedMethod(
                delegate () { retval = dtMyDateTime_m; }
            );
            return retval;
        }
        set
        {
            WriteLockedMethod(
                delegate () { dtMyDateTime_m = value; }
            );
        }
    }

    private void ReadLockedMethod(Action method)
    {
        rwlMyLock_m.AcquireReaderLock(0);
        try
        {
            method();
        }
        finally
        {
            rwlMyLock_m.ReleaseReaderLock();
        }
    }

    private void WriteLockedMethod(Action method)
    {
        rwlMyLock_m.AcquireWriterLock(0);
        try
        {
            method();
        }
        finally
        {
            rwlMyLock_m.ReleaseWriterLock();
        }
    }
}
于 2009-05-31T19:54:03.297 に答える
0

方法2の方が良いと思います。

  • プロパティのコードをよりシンプルで読みやすくします。
  • ロックコードを何度も書き直す必要がないため、エラーが発生しにくくなります。
于 2008-11-10T19:41:04.767 に答える
-1

愚かな私。ロックされたメソッドを各インスタンスの一部にすることで、これをさらに簡単にする方法があります(以前の投稿のように静的ではありません)。`rwlMyLock_m'を他のクラスやメソッドに渡す必要がないので、これが本当に好きです。

class StackOTest
{
    private delegate DateTime ReadLockMethod();
    private delegate void WriteLockMethod();

    static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
    private DateTime dtMyDateTime_m;
    public DateTime MyDateTime
    {
        get
        {
            return ReadLockedMethod(
                delegate () { return dtMyDateTime_m; }
            );
        }
        set
        {
            WriteLockedMethod(
                delegate () { dtMyDateTime_m = value; }
            );
        }
    }

    private DateTime ReadLockedMethod(ReadLockMethod method)
    {
        rwlMyLock_m.AcquireReaderLock(0);
        try
        {
            return method();
        }
        finally
        {
            rwlMyLock_m.ReleaseReaderLock();
        }
    }

    private void WriteLockedMethod(WriteLockMethod method)
    {
        rwlMyLock_m.AcquireWriterLock(0);
        try
        {
            method();
        }
        finally
        {
            rwlMyLock_m.ReleaseWriterLock();
        }
    }
}
于 2009-05-31T19:29:41.133 に答える