2009-02-21 120 views
1

我有一個包含大量動態內容的ASP.NET應用程序。對於屬於特定客戶的所有用戶來說內容是相同的。爲了減少每個請求所需的數據庫命中數量,我決定緩存客戶端級別的數據。我創建了一個靜態類(「ClientCache」)來保存數據。
該類最常用的方法是迄今爲止的「GetClientData」,它返回包含特定客戶端的所有存儲數據的ClientData對象。不過,客戶端數據會被延遲加載:如果請求的客戶端數據已被緩存,調用者將獲取緩存的數據;否則,數據被提取,添加到緩存中,然後返回給調用者。ASP.NET /靜態類競爭條件?

最終我開始在ClientData對象添加到緩存的行上的GetClientData方法中發生間歇性崩潰。下面是方法體:

public static ClientData GetClientData(Guid fk_client) 
{ 
    if (_clients == null) 
     _clients = new Dictionary<Guid, ClientData>(); 

    ClientData client; 
    if (_clients.ContainsKey(fk_client)) 
    { 
     client = _clients[fk_client]; 
    } 
    else 
    { 
     client = new ClientData(fk_client); 
     _clients.Add(fk_client, client); 
    } 
    return client; 
} 

異常文本總是類似於「具有相同鍵的對象已存在」。 當然,我試圖編寫代碼,這樣如果客戶端已經存在,就無法將客戶端添加到緩存中。

在這一點上,我懷疑我有一個競爭條件,並且該方法正在同時執行兩次,這可以解釋代碼將如何崩潰。但是,我感到困惑的是,該方法如何可以同時執行兩次。據我所知,任何ASP.NET應用程序一次只能處理一個請求(這就是爲什麼我們可以使用HttpContext.Current)。

那麼,這個錯誤可能是一個競爭條件,需要把鎖定在關鍵部分?或者我錯過了一個更明顯的錯誤?

回答

3

如果ASP.NET應用程序一次只處理一個請求,則所有ASP.NET網站都會遇到嚴重問題。 ASP.NET一次可以處理數十個(通常每個CPU核25個)。

您應該使用ASP.NET緩存而不是使用自己的字典來存儲對象。緩存上的操作是線程安全的。

請注意,您需要確保對緩存中存儲的對象的讀操作是線程安全的,不幸的是,大多數.NET類只是聲明實例成員不是線程安全的,沒有嘗試指出任何可能的情況。

編輯

這個答案的狀態註釋: - 在高速緩存

只有原子操作是線程安全的。如果您執行類似檢查
如果一個密鑰存在,然後添加它,這不是線程安全的,並可能導致項目
覆蓋。

其值得指出的是,如果我們覺得我們需要使這樣的操作成爲原子,那麼緩存可能不是資源的合適位置。

我有相當多的代碼,完全按照評論描述。但是,在兩個地方存儲的資源都是相同的。因此,如果在罕見情況下的現有項目被覆蓋,則唯一的代價是一個線程不必要地產生資源。這種罕見事件的成本遠低於每次嘗試訪問它時試圖使操作成爲原子的成本。

+0

感謝您的解釋。我知道ASP.NET輸出緩存,但不知道緩存對象。緩存API將更加乾淨地解決我的問題。 – mschaad 2009-03-03 06:17:22

+0

只有高速緩存上的原子操作是線程安全的。如果你做了一些事情,比如檢查一個鍵是否存在,然後添加它,那不是線程安全的,並且會導致項被覆蓋。 – 2009-03-04 00:59:29

0

您的代碼確實假設只有一個線程在一個時間的函數。

這只是簡單的將不會在ASP.NET

是真的如果你堅持做這種方式,使用靜態信號鎖定圍繞這一類的區域。

2

這是很容易解決:

private _clientsLock = new Object(); 

public static ClientData GetClientData(Guid fk_client) 
{ 
    if (_clients == null) 
    lock (_clientsLock) 
     // Check again because another thread could have created a new 
     // dictionary in-between the lock and this check 
     if (_clients == null) 
     _clients = new Dictionary<Guid, ClientData>(); 

    if (_clients.ContainsKey(fk_client)) 
    // Don't need a lock here UNLESS there are also deletes. If there are 
    // deletes, then a lock like the one below (in the else) is necessary 
    return _clients[fk_client]; 
    else 
    { 
    ClientData client = new ClientData(fk_client); 

    lock (_clientsLock) 
     // Again, check again because another thread could have added this 
     // this ClientData between the last ContainsKey check and this add 
     if (!clients.ContainsKey(fk_client)) 
     _clients.Add(fk_client, client); 

    return client; 
    } 
} 

請記住,當你用靜態類亂七八糟的,你有一個線程同步問題的可能性。如果有一些類型的靜態類級列表(在這種情況下,_clients的Dictionary對象),有DEFINITELY將是線程同步的問題來處理。

0

你需要線程安全&減少鎖定。
見雙檢鎖(http://en.wikipedia.org/wiki/Double-checked_locking

寫簡單地用TryGetValue。


public static object lockClientsSingleton = new object(); 

public static ClientData GetClientData(Guid fk_client) 
{ 
    if (_clients == null) { 
     lock(lockClientsSingleton) { 
      if(_clients==null) { 
       _clients = new Dictionary``(); 
      } 
     } 
    } 
    ClientData client; 
    if(!_clients.TryGetValue(fk_client, out client)) 
    { 
     lock(_clients) 
     { 
      if(!_clients.TryGetValue(fk_client, out client)) 
      { 
       client = new ClientData(fk_client) 
       _clients.Add(fk_client, client); 
      } 
     } 
    } 
    return client; 
}