2011-10-23 34 views
3

我需要跟蹤順序標識,這是通過SP在4個表中執行max(id)的操作返回給我的,在管理順序的db中沒有標識符/順序。這顯然會產生併發問題,所以我創建了一個輔助類來確保始終生成唯一的Id。以下幫助程序類線程安全嗎?

幫助程序通過其存儲庫進行初始化,該存儲庫最初調用數據庫以查找當前Id,隨後通過幫助程序在內存中爲後續的Id請求提供服務。將只有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

對於剩餘字段,我使用Pascal大小寫來表示只讀/常量和_Camel大小寫的字段。只有一個存儲庫使用類 – Tom

回答

8

這是線程安全的,但它不必要的緩慢。
你不需要一個鎖來增加一個數字;相反,你可以使用原子數學。

另外,您正在共享所有實例的鎖(它是static),這是不必要的。 (一次運行兩個不同的實例沒有任何問題)

最後,(恕我直言)沒有一個單獨的未初始化狀態。

我會寫這樣的:

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

是的,它是線程安全的;然而,國際海事組織的這個鎖太全球化了 - 一個用於保護實例數據的靜態鎖可能會導致一些過度殺傷。

此外,NextId作爲一個屬性是不好的 - 它改變狀態,所以應該是一個方法。

雖然這會改變大部分課程,但您也可能更喜歡Interlocked.Increment over lock。

最後,在SetId中 - 如果它已經被初始化了,我會拋出一個異常(InvalidOperationException)而不是盲目地忽略這個調用 - 這聽起來像是一個錯誤。當然,這會在檢查Initialized和調用SetId之間引入一個棘手的時間間隔 - 如果SetId做出更改,則返回true;如果返回true,則返回false,但SLaks的方法更好。

+0

這就是我對賽跑狀況的擔憂。它是一個Web應用程序,因此多個存儲庫實例(根據請求創建的回購)可以調用GetNextTransactionId()。我推斷,如果它的初始化,那麼我不在乎,只要繼續並獲得一個Id作爲助手將保證唯一性 – Tom

0

我不認爲這是一個好主意,你應該找到另一種方法來解決這個問題。 通常當需要真正獨特的ID並且沒有一種計算有效的方法來檢查ID是否被使用時,我會使用GUID。

但是,您可以只使用互鎖操作而不是鎖定,您可以不鎖定。

查找Interlocked.Increment,Interlocked.Exchange和Interlocked.CompareExchange

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

這會給你一個警告,但它是正常的,也是在C#中的文件作爲法律還報告。所以忽略一個不錯的編譯的警告:

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

如果您不需要檢查將IsInitialized你能做到這一點的最簡單的方式:

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

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