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


81

읽기 / 쓰기 잠금을 사용할 속성이 많이 있습니다. 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();
    }
}

1
많은 답변에서 이미 언급했듯이 방법 2는 매우 좋지만 잠금을 사용할 때마다 힙에 가비지가 발생하지 않도록하려면 ReadLock 및 WriteLock을 구조체로 변경해야합니다. using 문이 구조체의 IDisposable 인터페이스를 사용하더라도 C #은 박싱을 피할만큼 영리합니다!
Michael Gehling

답변:


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

2
@bjan 음, 왜 using그런 경우를 고려 하고 있습니까? 그게 목적이 아닙니다 using.
chakrit

1
이것이 블록을 try/catch통해 처리하는 유일한 방법으로 보이는 이유이기도 try/catch/finally합니다. 희망 using도 이것을 처리 할 수 ​​있습니다
bjan

글쎄, try/finally그럴 경우에는 네가 유일한 선택이라고 생각합니다. 하지만 IMO는 이러한 경우 (Dispose ()가 항상 호출되어야하는 경우) 객체의 수명을 유지하는 책임이있는 객체 / 코드 조각이 항상 있어야한다고 생각합니다. 한 클래스가 인스턴스화 만 처리하고 다른 사람이 기억해야하는 경우 처리하기 위해 약간의 냄새가 나는 것 같습니다. 언어 수준에서 어떻게 추가할지 모르겠습니다.
chakrit

"사용"을 피하는 유일한 이유는 내가 추측하는 또 다른 개체 인스턴스화이기 때문에 일회용을 완전히 피하는 것이라고 생각합니다.
Demetris Leptos

12

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

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

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


안전 장치는 어떻습니까? 마침내 시도가 항상 finally 블록을 실행한다는 것을 알고 있습니다. dispose가 호출되지 않는 방법이 있습니까?
Jeremy

1
아니요, using 문은 본질적으로 try / finally 패턴의 구문 설탕입니다.
Blair Conrad

사용 모델은 개체가 항상 삭제되도록합니다.
Nathen Silver

리플렉터를 사용하면 컴파일러에 의해 using 블록이 try ... finally 구성으로 변환되어 IL 수준에서 동일하다는 것을 알 수 있습니다. '사용'은 단지 통역 적 설탕입니다.
Rob Walker

^^ 드디어 호출되지 않는 잠재적 시나리오가 있음을 기억하십시오. 예를 들어 정전. ;)
퀴 블썸

8

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

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가 도움이 될 수 있다고 확신합니다 ;-)


2
마지막 시도가 반드시 예외를 가리는 것은 아닙니다. 내 예에서 잠금이 요청 된 다음 범위 내에서 예외가 발생하면 잠금이 해제되지만 예외는 여전히 발생합니다.
Jeremy

1
@Jeremy : finally 블록이 예외를 throw하면 try 블록에서 throw 된 예외가 마스킹됩니다. 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"문을 하나의 메서드로 분리하고 개체의 사용을 "try"/ "catch"블록을 사용하여 다른 메서드로 분리했습니다. 관련 객체에 대해 이와 같은 여러 "using"문을 중첩 할 수 있습니다 (가끔 프로덕션 코드에서 3 ~ 4 개 깊이로 이동합니다).

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

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

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


팁, 코드 가독성을 위해 : 내부 "using"을 제외하고 중괄호를 사용하지 않고 uses 문을 정렬하고 압축합니다.
벤트 트란 베르

4

세 번째 옵션이 좋아요

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

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

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


lock 문은 ReaderWriterLock과 같은 방식으로 작동하지 않습니다.
chakrit

@chakrit : 아니요,하지만 실제로 잠금 경합이 있다는 것을 알지 못하면 성능이 더 우수 할 것입니다.
Michael Burr

사실이지만 항상 작은 자물쇠를 먼저 사용해야합니다. Subby의 질문은 잠금을 허용하지 않는 성능 문제 또는 요구 사항에 대해 언급하지 않습니다. 따라서 매끄럽게 시도 할 이유가 없습니다. 잠그고 흔들면됩니다.

4

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

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


네, 분명히 당신의 요점을 봅니다. 내 속성의 대부분은 실제로 bool, int이며, 원자 적이어야하므로 잠글 필요가 없습니다. 잠금을 설정하고 싶은 datetime, 문자열이 있습니다. 소수가 잠금을해야하기 때문에, 나는 재산에 넣어하는 것이 가장 thight
제레미

4

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


1

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

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

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

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


0

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

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

0

잠금의 세분성 및 의심스러운 예외 처리를 포함하여 위의 많은 의견에 동의하지만 문제는 접근 방식 중 하나입니다. 내가 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 %의 성능 오버 헤드를 갖습니다.


0

아무도 익명 함수에서 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();
        }
    }
}

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.