Кажется, вы хотите избежать ненужного обращения к источнику данных. Затем, если многие потоки одновременно запрашивают одно и то же, вы хотите разрешить запрашивать источник данных и кэшировать данные и удерживать остальных до тех пор, пока данные не будут заполнены.
Возможно, у вас есть один 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
, вы можете использовать распределенную блокировку как LockTake/LockRelease от StackExchange.Redis.