tg-me.com/csharp_1001_notes/680
Last Update:
📌 Задача: "Высоконагружённый кэш с автоматической очисткой и конкурентным доступом"
❗️Условие:
Реализуйте класс SmartCache<TKey, TValue>
в .NET, который должен:
- Позволять безопасно добавлять и получать элементы из кэша в многопоточной среде (`Get`, `Set`).
- Автоматически удалять элементы через N секунд после их добавления (TTL).
- Поддерживать высокую производительность при массовом доступе (тысячи операций в секунду).
- Минимизировать блокировки (`lock`) или использовать неблокирующие структуры.
- Корректно работать с истекшими элементами:
- Не возвращать их через Get
.
- Не копить мусор в памяти.
---
▪️ Ограничения:
- Можно использовать стандартные коллекции .NET (`ConcurrentDictionary`, Timer
, Task
, CancellationToken
и т.д.).
- Нельзя использовать внешние библиотеки типа MemoryCache
, Redis
, LazyCache
и др.
- Нужно поддерживать работу под большой нагрузкой (много ключей и операций параллельно).
---
▪️ Подсказки:
- Для конкурентного доступа подойдёт ConcurrentDictionary<TKey, ValueWithExpiry>
.
- Для очистки устаревших данных:
- Можно использовать фоновую задачу (`Task`) с таймером, которая периодически чистит старые записи.
- Обратите внимание на гонки состояний: между проверкой срока жизни элемента и его удалением.
---
▪️ Что оценивается:
- Умение проектировать потокобезопасные структуры данных.
- Продуманность балансировки между скоростью операций и частотой очистки.
- Правильная работа со временем жизни (`TTL`).
- Чистота и лаконичность кода.
---
▪️ Разбор возможного решения:
▪️ Основная идея:
- В кэше храним не просто значение, а пару (значение + время истечения).
- При Get(key)
:
- Проверяем, истёк ли элемент.
- Если истёк — удаляем его и возвращаем null
или default
.
- При Set(key, value)
:
- Сохраняем значение с текущим временем + TTL.
- Отдельная фоновая задача (`Task`) регулярно сканирует кэш и удаляет устаревшие элементы.
▪️ Мини-пример структуры:
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
public class SmartCache<TKey, TValue>
{
private readonly ConcurrentDictionary<TKey, (TValue Value, DateTime Expiry)> _cache = new();
private readonly TimeSpan _ttl;
private readonly CancellationTokenSource _cts = new();
public SmartCache(TimeSpan ttl)
{
_ttl = ttl;
StartCleanupTask();
}
public void Set(TKey key, TValue value)
{
_cache[key] = (value, DateTime.UtcNow.Add(_ttl));
}
public TValue Get(TKey key)
{
if (_cache.TryGetValue(key, out var entry))
{
if (entry.Expiry > DateTime.UtcNow)
{
return entry.Value;
}
else
{
_cache.TryRemove(key, out _);
}
}
return default;
}
private void StartCleanupTask()
{
Task.Run(async () =>
{
while (!_cts.Token.IsCancellationRequested)
{
foreach (var key in _cache.Keys)
{
if (_cache.TryGetValue(key, out var entry) && entry.Expiry <= DateTime.UtcNow)
{
_cache.TryRemove(key, out _);
}
}
await Task.Delay(TimeSpan.FromSeconds(30), _cts.Token); // периодическая очистка
}
});
}
public void Dispose()
{
_cts.Cancel();
}
}
📌 Важные моменты:
- Кэш конкурентный (`ConcurrentDictionary`) — доступ без явных блокировок.
- Периодическая чистка не мешает основным операциям.
- Удаление истёкших элементов происходит "мягко" (через проверку срока жизни).
- Фоновая задача корректно завершается через
CancellationToken
.BY C# 1001 notes
Warning: Undefined variable $i in /var/www/tg-me/post.php on line 283
Share with your friend now:
tg-me.com/csharp_1001_notes/680