web-dev-qa-db-ja.com

「using」ステートメントと「try finally」

読み取り/書き込みロックを使用するプロパティの束を持っています。 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();
    }
}
72
Jeremy

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

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

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

12
Rob Walker

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

tryなしのcatchは明らかに悪い考えです。 [〜#〜] 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;}を使用できないと仮定すると、アプローチを再検討するかもしれません。もっと情報があればStack Overflowが役立つと確信しています;-)

8
Steven A. Lowe

私は個人的に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
    }
}
_

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

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

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

一方、「使用」の範囲とクリーンアップの利点により、これは私の最もお気に入りのC#機能の1つになります。ちなみに、私はJava、C#、およびPythonを主要な言語として使用しています。他の多くの言語はあちこちに投げ込まれています。それは実用的で、毎日の主力製品だからです。

5
Rob Williams

3番目のオプションが好き

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

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

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

4
Will

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

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

4
Hans Passant

[〜#〜] dry [〜#〜] と言う:2番目のソリューション。最初のソリューションはロックを使用するロジックを複製しますが、2番目のソリューションはそうではありません。

3
Tetha

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

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

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

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

1
Luke

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

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();
        }
    }
}
0
Matthew

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();
        }
    }
}
0
Matthew

実際、最初の例では、ソリューションを比較可能にするために、「IDisposable」も実装し、ロックを直接解除する代わりに「Finally」ブロックから「Dispose」を呼び出します。そうすれば、「リンゴからリンゴへ」の実装(およびMSIL)になります(MSILは両方のソリューションで同じです)。スコープが追加され、フレームワークが「IDisposable」の適切な使用を保証するため、「使用」を使用することをお勧めします(「IDisposabe」を自分で実装する場合、後者はあまり有益ではありません)。

0
user1172173

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

  • プロパティ内のよりシンプルで読みやすいコード。
  • ロックコードを何度も書き換える必要がないため、エラーが発生しにくくなります。
0
Nathen Silver

以下は、ReaderWriterLockSlimクラスの拡張メソッドを作成します。これにより、次のことが可能になります。

var rwlock = new ReaderWriterLockSlim();
using (var l = rwlock.ReadLock())
{
     // read data
}
using (var l = rwlock.WriteLock())
{
    // write data
}

コードは次のとおりです。

static class ReaderWriterLockExtensions() {
    /// <summary>
    /// Allows you to enter and exit a read lock with a using statement
    /// </summary>
    /// <param name="readerWriterLockSlim">The lock</param>
    /// <returns>A new object that will ExitReadLock on dispose</returns>
    public static OnDispose ReadLock(this ReaderWriterLockSlim readerWriterLockSlim)
    {
        // Enter the read lock
        readerWriterLockSlim.EnterReadLock();
        // Setup the ExitReadLock to be called at the end of the using block
        return new OnDispose(() => readerWriterLockSlim.ExitReadLock());
    }
    /// <summary>
    /// Allows you to enter and exit a write lock with a using statement
    /// </summary>
    /// <param name="readerWriterLockSlim">The lock</param>
    /// <returns>A new object that will ExitWriteLock on dispose</returns>
    public static OnDispose WriteLock(this ReaderWriterLockSlim rwlock)
    {
        // Enter the write lock
        rwlock.EnterWriteLock();
        // Setup the ExitWriteLock to be called at the end of the using block
        return new OnDispose(() => rwlock.ExitWriteLock());
    }
}

/// <summary>
/// Calls the finished action on dispose.  For use with a using statement.
/// </summary>
public class OnDispose : IDisposable
{
    Action _finished;

    public OnDispose(Action finished) 
    {
        _finished = finished;
    }

    public void Dispose()
    {
        _finished();
    }
}
0
Clint Chapman

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

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

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

interface ILock {
    IDisposable Acquire();
}

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

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

0
Ajaxx