看來你想避免不必要地調用數據源。然後,如果許多線程同時請求相同的內容,則希望允許查詢數據源並緩存數據,並保留其他數據直到填充數據。
您可能希望有一個decorator,以確保只有一個查詢類型在同一時間執行。您可以使用處於線程安全集合中的對象來表示查詢,並在查詢執行期間對其進行鎖定。
因此,考慮表達您查詢您的數據源的方式的接口:
interface IQueryExecuter<TQuery, TResult>
{
TResult Execute(TQuery query);
}
你可以使用一個線程安全的裝飾對象緩存查詢結果,如果查詢結果不被緩存,只有一個線程執行查詢到數據源:
未經測試的代碼!
class QueryThrottler<TQuery, TResult> : IQueryExecuter<TQuery, TResult>
{
// do not lock on external objects
class QueryObject
{
public TQuery Query { get; set; }
}
readonly IQueryExecuter<TQuery, TResult> _inner;
readonly ConcurrentDictionary<TQuery, QueryObject> _queries;
public QueryThrottler(IQueryExecuter<TQuery, TResult> inner)
{
_queries = new ConcurrentDictionary<TQuery, QueryObject>();
_inner = inner;
}
public TResult Execute(TQuery query)
{
// if it is on cache return the result
TResult result;
if (!IsCached(query, out result))
{
// otherwise lock other threads
// on the same query
var queryObject = _queries.GetOrAdd(query, k => new QueryObject() { Query = k });
lock (queryObject)
{
// double check it is not cached already
if (!IsCached(query, out result))
{
result = _inner.Execute(queryObject.Query);
PopulateCache(query, result);
}
}
}
return result;
}
private void PopulateCache(TQuery query, TResult result)
{
// Save the result in Redis using TQuery as key
}
private bool IsCached(TQuery query, out TResult result)
{
// go to redis and check if the query is cached using TQuery as key
// if exists, set the result out parameter and return true
// otherwise, return false
result = default(TResult);
return false;
}
}
此代碼依賴於TQuery的具有GetHashCode
和Equals
適當的實施方式。
修飾對象(構造函數中的inner
)是將對數據源執行實際查詢的對象。
如果你有許多服務器,並希望確保只有一個線程從所有服務器進行實際查詢到的數據源,而不是lock
,你可以從StackExchange.Redis使用分佈式鎖像LockTake/LockRelease。