'using'ステートメントvs'tryfinally '


81

読み取り/書き込みロックを使用するプロパティがたくさんあります。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();
    }
}

1
多くの回答ですでに述べたように、方法2は非常に優れていますが、ロックを使用するたびにヒープ上のゴミを避けるために、ReadLockとWriteLockを構造体に変更する必要があります。usingステートメントは構造体のIDisposableインターフェイスを使用しますが、C#はボクシングを回避するのに十分賢いです!
MichaelGehling20年

回答:


95

MSDNから、ステートメントを使用(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チェックと変数のスコープが追加されています。ドキュメントには、「IDisposableオブジェクトの正しい使用が保証される」と記載されているため、将来、あいまいなケースに対してさらに優れたフレームワークサポートを取得できる可能性があります。

したがって、オプション2を使用してください。

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


usingステートメント内でインスタンス化できない、再利用できない、または出力パラメータとして渡されないリソースを解放するのに最適なものは何ですか?try / catch !!!
bjan 2012年

2
@bjanええと、なぜusingその場合も検討しているのですか?それusingは目的ではありません。
シャクリット2012年

1
それがブロックtry/catchを介して処理する唯一の方法に見えるので、私も言及する理由try/catch/finallyです。usingこれも処理できると期待されていた
bjan 2012年

そうですね、try/finallyその場合の唯一の選択肢だと思います。ただし、IMOは、そのような場合にオブジェクトの存続期間を維持する責任があるオブジェクト/コードの一部が常に存在する必要があると思います(Dispose()が常に呼び出される必要があります)。1つのクラスがインスタンス化のみを処理し、他の誰かが覚えておく必要がある場合処分するのはちょっと臭いと思います。言語レベルでそれをどのように追加するかわからない。
シャクリット2012年

「使用」を避ける唯一の理由は、使い捨てを完全に避けることだと思います。それは、私が推測するさらに別のオブジェクトのインスタンス化だからです。
Demetris Leptos 2017

12

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

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

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


フェイルセーフになるのはどうですか?トライファイナルは常にファイナルブロックを起動することを知っていますが、ディスポーズが呼び出されない方法はありますか?
ジェレミー

1
いいえ、usingステートメントは本質的にtry / finallyパターンの構文糖衣です。
ブレアコンラッド

使用モデルは、オブジェクトが常に廃棄されることを保証します。
ナテンシルバー

リフレクターを使用する場合、usingブロックがコンパイラーによってtry ... final構文に変換されることがわかります。したがって、ILレベルではそれらは同等です。「使用」は単なる糖衣構文です。
ロブウォーカー

^^最終的に呼び出されない潜在的なシナリオがあることを忘れないでください。たとえば、停電。;)
厄介な2008年

8

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

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

また、MicrosoftはReaderWriterLockSlimを推奨するようになりました。ではなく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;}、アプローチを再考するかもしれません。詳細については、StackOverflowが役立つと確信しています;-)


2
最終的に試行しても、必ずしも例外がマスクされるとは限りません。私の例では、ロックが取得され、スコープ内で例外がスローされた場合、ロックは解放されますが、例外はバブルアップします。
ジェレミー

1
@Jeremy:finallyブロックが例外をスローすると、tryブロックでスローされた例外がマスクされます。これは、msdnの記事で、構文の使用に関する(同じ)問題であると述べていました
Steven A. Lowe

3
例外をマスクするのではなく、まったく同じ方法で例外を置き換えます。
ジョンハンナ

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つです。 。


ヒント、コードを読みやすくするために:内側の「using」を除いて中括弧を使用しないことにより、usesステートメントを整列および圧縮します。
ベントトランバーグ2017

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番目のオプションは、何が起こっているのかを最もクリーンで理解しやすいものです。


lockステートメントは、ReaderWriterLockと同じようには機能しません。
chakrit 2008年

@chakrit:いいえ。ただし、実際にロックの競合があることがわかっていない限り、パフォーマンスが向上する可能性があります。
Michael Burr

本当ですが、常に最初にスマイルロックを選択する必要があります。サブビーの質問には、ロックを許可しないパフォーマンスの問題や要件については何も記載されていません。したがって、なめらかにしようとする理由はありません。ロックして揺すります。

4

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

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


はい、私は間違いなくあなたの主張を理解しています。私のプロパティのほとんどは実際にはbools、intsであり、アトミックである必要があるため、ロックする必要はありません。ロックをオンにしたい日時の文字列のものがいくつかあります。少数派は錠を必要とするので、私はそれを財産に置くのが最善だと思います
Jeremy

4

DRYは言う:2番目の解決策。最初のソリューションはロックを使用するロジックを複製しますが、2番目のソリューションは複製しません。


1

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

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

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

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


0

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

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

0

ロックの粒度や疑わしい例外処理など、上記のコメントの多くに同意しますが、質問はアプローチの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、使用テクニックとtry-finalのオーバーヘッドを測定しました。使い捨てオブジェクトを割り当てるオーバーヘッドは、2〜3%のパフォーマンスオーバーヘッドになります。


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

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

0

以下は、以下を実行できる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

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

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


-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();
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.