developer tip

'사용'문 대 '마지막으로 시도'

copycodes 2020. 10. 24. 10:36
반응형

'사용'문 대 '마지막으로 시도'


읽기 / 쓰기 잠금을 사용할 속성이 많이 있습니다. try finally또는 using절을 사용하여 구현할 수 있습니다 .

에서 try finally나는 전에 잠금을 획득 try하고 finally. 에서 using절, 나는 그것의 폐기 방법에 생성자에서 잠금을 획득 클래스 및 자료를 만들 것입니다.

저는 많은 곳에서 읽기 / 쓰기 잠금을 사용하고 있으므로 .NET보다 간결한 방법을 찾고 있습니다 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();
    }
}

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로 이동하십시오.

더 이상 필요하지 않은 직후에 끝나는 범위 내에 변수 가있는 것도 장점입니다.


나는 확실히 두 번째 방법을 선호합니다. 사용 시점에서 더 간결하고 오류 발생 가능성이 적습니다.

첫 번째 경우 코드를 편집하는 사람은 Acquire (Read | Write) Lock 호출과 try 사이에 아무것도 삽입하지 않도록주의해야합니다.

(이와 같은 개별 속성에 대해 읽기 / 쓰기 잠금을 사용하는 것은 일반적으로 과잉입니다. 훨씬 더 높은 수준에서 적용하는 것이 가장 좋습니다. 잠금이 유지되는 시간을 고려할 때 경합 가능성이 매우 작기 때문에 여기에서는 간단한 잠금으로 충분합니다. 읽기 / 쓰기 잠금을 획득하는 것은 단순 잠금보다 비용이 많이 드는 작업입니다.)


두 솔루션 모두 예외를 마스킹 하기 때문에 나쁠 가능성을 고려하십시오 .

a가 try없는 것은 catch분명히 나쁜 생각이어야합니다. 성명도 마찬가지로 위험한 이유는 MSDN참조하십시오 using.

Microsoft는 이제 ReaderWriterLock 대신 ReaderWriterLockSlim권장 합니다.

마지막으로 Microsoft 예제에서는 이러한 문제를 방지하기 위해 두 개의 try-catch 블록사용 합니다.

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

간단하고 일관 적이며 깨끗한 솔루션이 좋은 목표이지만을 사용할 수 없다고 가정하면 lock(this){return mydateetc;}접근 방식을 재고 할 수 있습니다. 더 많은 정보를 통해 Stack Overflow가 도움이 될 수 있다고 확신합니다 ;-)


저는 개인적으로 가능한 한 자주 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"문을 하나의 메서드로 분리하고 개체의 사용을 "try"/ "catch"블록을 사용하여 다른 메서드로 분리했습니다. 관련 개체에 대해 이와 같은 여러 "using"문을 중첩 할 수 있습니다 (가끔 프로덕션 코드에서 3 ~ 4 개 깊이로 이동합니다).

내에서 Dispose()이러한 사용자 지정하는 방법 IDisposable클래스, 나는 예외 (하지만 오류)를 잡아 (Log4net 사용)을 기록합니다. 이러한 예외가 내 처리에 영향을 미칠 수있는 상황을 본 적이 없습니다. 평소와 같이 잠재적 인 오류는 호출 스택을 전파하고 일반적으로 적절한 메시지 (오류 및 스택 추적)가 기록 된 상태로 처리를 종료 할 수 있습니다.

에서 중대한 예외가 발생할 수있는 상황이 발생하면 Dispose()해당 상황에 맞게 재 설계합니다. 솔직히 나는 그런 일이 일어날 것 같지 않다.

한편, "사용"의 범위와 정리 이점은 제가 가장 좋아하는 C # 기능 중 하나입니다. 그건 그렇고, 저는 Java, C # 및 Python을 기본 언어로 사용하고 여기 저기 많은 다른 언어를 사용합니다. "사용"은 실용적이고 일상적인 일이기 때문에 가장 좋아하는 언어 기능 중 하나입니다. .


세 번째 옵션이 좋아요

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

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

두 가지 옵션 중 두 번째 옵션은 무슨 일이 일어나고 있는지 가장 깨끗하고 이해하기 쉽습니다.


"속성 무리"및 속성 getter 및 setter 수준의 잠금이 잘못 보입니다. 잠금이 너무 세분화되어 있습니다. 대부분의 일반적인 객체 사용 에서 동시에 이상의 속성에 액세스 하기 위해 잠금을 획득했는지 확인하고 싶을 것 입니다. 귀하의 특정 사례는 다를 수 있지만 다소 의심 스럽습니다.

어쨌든, 속성 대신 객체에 액세스 할 때 잠금을 획득하면 작성해야하는 잠금 코드의 양을 크게 줄일 수 있습니다.


DRY 말한다 : 두 번째 솔루션. 첫 번째 솔루션은 잠금 사용 논리를 복제하는 반면 두 번째 솔루션은 그렇지 않습니다.


Try / Catch 블록은 일반적으로 예외 처리를위한 것이며 블록을 사용하여 개체가 삭제되었는지 확인하는 데 사용됩니다.

읽기 / 쓰기 잠금의 경우 try / catch가 가장 유용 할 수 있지만 다음과 같이 둘 다 사용할 수도 있습니다.

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

묵시적으로 IDisposable 인터페이스를 호출하고 예외 처리를 간결하게 만들 수 있습니다.


방법 2가 더 좋을 것이라고 생각합니다.

  • 속성에서 더 간단하고 읽기 쉬운 코드.
  • 잠금 코드를 여러 번 다시 작성할 필요가 없으므로 오류가 발생하기 쉽습니다.

잠금의 세분성 및 의심스러운 예외 처리를 포함하여 위의 많은 의견에 동의하지만 문제는 접근 방식 중 하나입니다. 내가 try {} finally model ... 추상화보다 사용하는 것을 선호하는 한 가지 큰 이유를 말씀 드리겠습니다.

한 가지 예외를 제외하고는 귀하와 매우 유사한 모델이 있습니다. 기본 인터페이스 ILock을 정의하고 Acquire ()라는 하나의 메서드를 제공했습니다. Acquire () 메서드는 IDisposable 개체를 반환했으며 결과적으로 내가 다루는 개체가 잠금 범위를 수행하는 데 사용할 수있는 ILock 유형 인 한 의미합니다. 이것이 왜 중요한가요?

우리는 다양한 잠금 메커니즘과 동작을 다룹니다. 잠금 개체에는 사용하는 특정 제한 시간이있을 수 있습니다. 잠금 구현은 모니터 잠금, 리더 잠금, 작성기 잠금 또는 스핀 잠금 일 수 있습니다. 그러나 호출자의 관점에서 볼 때 그 모든 것이 관련이 없으며 리소스를 잠그는 계약이 적용되고 잠금이 구현과 일치하는 방식으로이를 수행한다는 점에 관심이 있습니다.

interface ILock {
    IDisposable Acquire();
}

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

나는 당신의 모델을 좋아하지만 발신자로부터 잠금 메커니즘을 숨기는 것을 고려할 것입니다. FWIW, 저는 try-finally와 비교하여 사용 기술의 오버 헤드를 측정했으며 일회용 개체를 할당하는 오버 헤드는 2-3 %의 성능 오버 헤드를 갖습니다.


아무도 익명 함수에서 try-finally 캡슐화를 제안하지 않은 것에 놀랐습니다. using 문을 사용하여 클래스를 인스턴스화하고 삭제하는 기술과 마찬가지로 한곳에서 잠금을 유지합니다. 잠금 해제를 생각할 때 "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();
        }
    }
}

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

다음은 다음을 수행 할 수있는 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();
    }
}

실제로 첫 번째 예에서 솔루션을 비교할 수 있도록하려면 IDisposable거기 에서도 구현 해야합니다. 그런 다음 잠금을 직접 해제하는 대신 블록 Dispose()에서 호출 finally합니다.

Then you'd be "apples to apples" implementation (and MSIL)-wise (MSIL will be the same for both solutions). It's still probably a good idea to use using because of the added scoping and because the Framework will ensure proper usage of IDisposable (the latter being less beneficial if you're implementing IDisposable yourself).


Silly me. There's a way to make that even simpler by making the locked methods part of each instance (instead of static like in my previous post). Now I really prefer this because there's no need to pass `rwlMyLock_m' off to some other class or method.

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

참고URL : https://stackoverflow.com/questions/278902/using-statement-vs-try-finally

반응형