2017-02-28 76 views
0

我們的設置是:使用AutoFac for DI的Asp.NET + MVC5。使用線程安全更新單例的屬性

我們有一個類(這是一個單),其管理訪問令牌的各種服務。無論何時,這些令牌太接近到期(少於10分鐘),我們要求新的令牌,刷新它們。我目前的實現如下所示:

// member int used for interlocking 
int m_inter = 0; 

private string Token { get; set; } 

private DateTimeOffset TokenExpiry { get; set; } 

public SingletonClassConstructor() 
{ 
    // Make sure the Token has some value. 
    RefreshToken(); 
} 

public string GetCredentials() 
{ 
    if ((TokenExpiry - DateTimeOffset.UTCNow).TotalMinutes < 10) 
    { 
    if (Interlocked.CompareExchange(ref m_inter, 1, 0) == 0) 
    { 
     RefreshToken(); 
     m_inter = 0; 
    } 
    } 

    return Token; 
} 

private void RefreshToken() 
{ 
    // Call some stuff 
    Token = X.Result().Token; 
    TokenExpiry = X.Result().Expiry; 
} 

正如您所看到的,Interlocked確保只有一個線程通過,其餘的獲取舊的令牌。我想知道的是 - 我們最終會出現一種奇怪的情況:當令牌被覆蓋時,另一個線程嘗試讀取,而不是舊的令牌,得到一個部分搞砸的結果?這個實現的任何問題?

謝謝!

+2

爲什麼你不只是使用鎖? – Servy

回答

3

對我來說,這個實現的最大問題是,你可能會刷新令牌兩次或更多次爲一個單一的保質期。如果線程在檢查到期條件之後但在CompareExchange()之前暫停,則在第一個線程恢復之前,另一個線程可以完成整個刷新操作,包括重置m_inter。理論上這可以發生在任意多個線程上。

在你的代碼的其餘部分是不夠具體,無法發表評論。沒有Token類型的聲明,所以目前尚不清楚這是struct還是class。而你GetCredentials()方法聲明爲返回Credentials值,而是返回一個Token值,使代碼顯然不是真實的,甚至代碼。

如果Token類型是class,那麼其餘的實現可能沒問題。即使在x64平臺上也可以自動分配引用類型變量,因此檢索Token屬性值的代碼將看到舊的令牌或新的令牌,而不是一些損壞的中間狀態。 (當然,我假設Token對象本身是線程安全的,最好憑藉它是不可變的。)

個人而言,我不會爲CompareExchange()打擾。只需使用全面的C#lock聲明並完成它。包含同步塊中的整個操作:檢查到期時間,必要時替換令牌並返回令牌值,全部來自lock

根據您顯示的代碼,我認爲這將更有意義,封裝在房產本身整個事情並做出public。但一個或其他方式,只要代碼檢索標記值得到它通過同步的這部分代碼,最簡單,最可靠的方式來證明代碼是正確的方法是使用lock。如果發現性能問題,那麼您可以考慮更難以正確使用的替代實現。

+0

非常感謝@彼得 - 這是一個驚人的閱讀。爲了更好地理解一些東西,我實際上更新了代碼示例,你是對的,這不是真正的代碼。令牌是字符串類型。 我想問你2個後續問題。 1)你提到過線程被暫停。鑑於這是Asp.Net會在那個階段真的被暫停嗎?沒有io電話等。不能真正找出爲什麼一個線程會被暫停(理論上說,你絕對是對的,雖然 2)如果我要添加反模式 - 雙重檢查 - 所以另一個if語句正好在RefreshToken之前? –

+0

_「鑑於這是Asp.Net,它會在這個階段真正暫停嗎?」 - Windows爲了多任務目的定期暫停所有線程。即使是擁有大量核心(例如32,64等)的服務器,在OS下運行的線程數仍然多於核心線程,因此每個線程都必須定期暫停。這是不可避免的。 –

+0

_「如果我要添加反模式 - 雙重檢查 - 所以另一個if語句恰好在RefreshToken之前?」 - 是的,您可以在調用RefreshToken()之前再次檢查過期時間,這樣可以避免多重 - 刷新問題。 –