2011-10-23 3 views
3

순차적 ID를 추적 할 필요가 있습니다. 이것은 4 개의 테이블에서 최대 (ID)를 수행하는 SP를 통해 나에게 반환됩니다. 시퀀스를 관리하는 db에는 식별자/시퀀스가 ​​없습니다. 분명히 동시성 문제가 생길 것이므로 고유 ID가 항상 생성되도록 도우미 클래스를 만들었습니다.다음 도우미 클래스가 안전합니까?

도우미는 처음에는 현재 ID를 찾기 위해 DB를 호출하는 저장소를 통해 초기화되며 ID에 대한 모든 후속 요청은 도우미를 통해 메모리에서 제공됩니다. DB (광산)를 사용하는 앱이 1 개만 있으므로 누구도 따라 오지 않고 거래를 생성 할 필요가 없습니다. & ID가 동기화되지 않습니다. 나는 필자 스레드 saftey의 기초를 가지고 생각하지만, 누군가가 알려 주시기 바랍니다 수 :

private class TransactionIdProvider 
{ 
private static readonly object Accesslock = new object(); 
private int _transactionId; 
public int NextId 
{ 
    get 
    { 
     lock (Accesslock) 
     { 
      if(!Initialised) throw new Exception("Must Initialise with id first!!"); 
      return _transactionId++; 
     } 
    } 
} 

public bool Initialised { get; private set; } 

public void SetId(int id) 
{ 
    lock (Accesslock) 
    { 
     if (Initialised) return; 
     _transactionId = id; 
     Initialised = true; 
    } 
} 

public TransactionIdProvider() 
{ 
    Initialised = false; 
} 
} 

도우미 클래스가 저장소에 초기화되어, 도우미가 초기화 경쟁 조건에 대해 걱정 메신저 :

private static readonly TransactionIdProvider IdProvider = new TransactionIdProvider(); 

    public int GetNextTransactionId() 
    { 
     if(!IdProvider.Initialised) 
     { 
      // Ask the DB 
      int? id = _context.GetNextTransactionId().First(); 
      if (!id.HasValue) 
       throw new Exception("No transaction Id returned"); 
      IdProvider.SetId(id.Value); 
     } 

     return IdProvider.NextId; 
    } 
+0

이를 사용하여 둘 이상의 저장소가 그래서 좋은 프라그와 그 경고를 무시 ? – driis

+0

'_transactionId'가 아닌 경우'AccessLock'을'static'으로 선언하지 마십시오. 동일한 명명 체계를 사용하면 도움이됩니다. –

+0

나는 파스칼 케이스를 읽기 전용/상수 필드와 나머지 필드 용 _Camel 케이스로 사용합니다. 코드가 훨씬 더 간결하고 읽기 쉽기 때문에 오직 하나의 저장소 만이 클래스 – Tom

답변

8

스레드로부터 안전하지만 불필요하게 느립니다.
번호를 증가시키기 위해 잠금 장치가 필요하지 않습니다. 대신 원자 수학을 사용할 수 있습니다.

또한 모든 인스턴스에서 잠금을 공유하므로 (static) 불필요합니다. (한 번에 두 개의 다른 인스턴스를 실행하는 데는 아무런 문제가 없습니다.)

마지막으로 (IMHO) 별도의 초기화되지 않은 상태가 발생하지 않습니다.

나는이처럼 작성합니다

class TransactionIdProvider { 
    private int nextId; 
    public TransactionIdProvider(int id) { 
     nextId = value; 
    } 

    public int GetId() { 
     return Interlocked.Increment(ref nextId); 
    } 
} 
+3

+1을 사용하고 있습니다. 그러나 속도 차이는 무의미한 것으로 보입니다. –

+0

니스; 간단하고, 깨끗하고, 최소한이고, 우아하고, 자물쇠가 없습니다. –

+0

위대한, 빠른 대답 주셔서 감사합니다 :) – Tom

0

예는 스레드 안전; 그러나 IMO 잠금은 너무 광범위합니다. 인스턴스 데이터를 보호하기위한 정적 잠금이 약간의 과잉 공격을가합니다.

또한 속성으로서 NextId는 상태가 변경되므로 메서드가되어야합니다.

잠금 장치를 통해 인터록 (Interlocked.Increment)을 선호 할 수도 있지만 대부분의 경우 변경됩니다.

마지막으로, SetId - 이미 초기화 된 경우 나는 맹목적으로 호출을 무시하는 대신 예외 (InvalidOperationException)를 throw합니다. 이는 오류처럼 들립니다. 물론 Initialize와 SetId를 검사하는 사이에 까다로운 간격이 생깁니다. SetId가 변경된 경우 SetId가 true를 반환하고 Set의 시점에서 초기화 된 것으로 밝혀지면 거짓이 될 수 있지만 SLaks의 접근 방식은 더 좋습니다 .

+0

그건 내가 경쟁 조건에 대해 걱정하고 있던 곳이었습니다. 그것의 웹 애플 리케이션 그래서 여러 저장소 인스턴스 (요청 당 생성 된 레포) GetNextTransactionId()를 호출 할 수 있습니다. 나는 그 initialised 그때 내가 상관 없어, 그냥 계속하고 도우미가 고유성을 보장하므로 이드를 얻을 것이라고 추론했다. – Tom

0

나는 이것이 좋은 생각이라고 생각하지 않는다. 이것을 처리 할 다른 방법을 찾아야한다. 일반적으로 정말 고유 ID가 필요하고 ID가 사용되는지 계산적으로 유효한 방법이없는 경우 GUID를 사용합니다.

그러나 잠금 대신 연동 작업을 사용하면 잠금없이 작업을 수행 할 수 있습니다.

private class TransactionIdProvider 
{ 
    private volatile int _initialized; 
    private int _transactionId; 

    public int NextId 
    { 
     get 
     { 
      for (;;) 
      { 
       switch (_initialized) 
       { 
        case 0: throw new Exception("Not initialized"); 
        case 1: return Interlocked.Increment(ref _transactionId); 
        default: Thread.Yield(); 
       } 
      } 
     } 
    } 

    public void SetId(int id) 
    { 
     if (Interlocked.CompareExchange(ref _initialized, -1, 0) == 0) 
     { 
      Interlocked.Exchange(ref _transactionId, id); 
      Interlocked.Exchange(ref _initialized, 1); 
     } 
    } 
} 

이 Interlocked.Increment, Interlocked.Exchange 및 Interlocked.CompareExchange

에 대한

봐 당신에게 경고를 줄 것이다, 그러나 정상이며 또한 법적으로 C#을 문서에보고됩니다.당신이는 IsInitialized를 확인할 필요가없는 경우

// Disable warning "A reference to a volatile field will not be treated as volatile" 
#pragma warning disable 0420 

당신이 가장 간단한 방법으로 작업을 수행 할 수 있습니다 :

public int NextId() 
{ 
    return Interlocked.Increment(ref _transactionId); 
} 

public void Set(int value) 
{ 
    Interlocked.Exchange(ref _transactionId, value); 
} 
관련 문제