目标
用C#封装一个线程安全的缓存器,达到目标定时更新,错峰缓存,彻底的减少数据库IO瓶颈。
相关线程安全的技术说明:
在C#中,如果你需要使用一个线程安全的List,一般可以考虑使用System.Collections.Concurrent.ConcurrentBag或System.Collections.Concurrent.ConcurrentQueue,取决于你的需求。
- ConcurrentBag:它是一个无序的线程安全集合,允许多个线程同时添加和删除元素。适用于需要在多线程环境中快速添加和移除元素的情况。
- ConcurrentQueue:它是一个线程安全的先进先出(FIFO)队列,适用于需要在多线程环境中按顺序处理元素的情况。
选择使用哪个类型取决于你的具体需求,是要求元素按照特定顺序处理还是只需要线程安全地添加和删除元素。不过需要注意,虽然这些集合类型提供了线程安全性,但在多线程环境中仍然需要小心处理并发问题,以确保正确的操作顺序和数据完整性。
我的问题
一个线程不停的往缓存中添加数据,当数据超过100的时候,另外一个线程把这个缓存数据入库,同时清空这个缓存,怎么设计才不会导致线程和数据出现问题
实现方法
使用C#中的BlockingCollection来实现这种需求,它可以很好地满足要求。BlockingCollection是一个线程安全的集合,它支持阻塞操作,可以用于生产者-消费者场景,其中一个线程不断向集合中添加数据,而另一个线程在必要时阻塞并处理数据。
我们创建了一个BlockingCollection作为缓冲区,并设置了缓冲区的大小为100。生产者线程不断向缓冲区中添加数据,而消费者线程通过GetConsumingEnumerable方法阻塞地获取数据并处理。当数据超过100时,消费者线程可以执行入库操作,然后清空缓冲区。
最后附加说明:
添加一个扩展方法,用来清空BlockingCollection
具体实现
实现一个单例的CacheManager,当触发数据极限的时候自动触发更新事件,或者可以手动主动触发
using System.Collections.Concurrent;
using Tools;
namespace SpacePhoneAPI.BackTask
{
public class CacheManager<T>
{
private static CacheManager<T>? _instance;
private static readonly object LockObject = new object();
private BlockingCollection<T> buffer;
private int MaxCache = 100;
public delegate Task MyEventHandler(List<T> list);
public event MyEventHandler? UpdateDataEvent;
public CacheManager(int maxCache)
{
MaxCache = maxCache;
buffer = new BlockingCollection<T>(MaxCache);
}
public static CacheManager<T> GetInstance(int maxCache)
{
if (_instance == null)
{
lock (LockObject)
{
if (_instance == null)
{
_instance = new CacheManager<T>(maxCache);
}
}
}
return _instance;
}
public int GetCount()
{
return buffer.Count;
}
/// <summary>
/// 添加数据,触发被动更新
/// </summary>
/// <returns></returns>
public void AddData(T data)
{
buffer.Add(data);
NLogHelper.Debug("debug:" + buffer.Count.ToString());
if (buffer.Count >= MaxCache)
{
UpdateDataEvent?.Invoke(buffer.ToList());
buffer.Clear();
}
}
/// <summary>
/// 主动更新
/// </summary>
/// <returns></returns>
public async Task InitiativeUpdate()
{
await Task.Run(() =>
{
if (buffer.Count > 0)
{
UpdateDataEvent?.Invoke(buffer.ToList());
buffer.Clear();
}
});
}
}
}
扩展方法
public static class ExtendBlockingCollection
{
public static void Clear<T>(this BlockingCollection<T> blockingCollection)
{
if (blockingCollection == null)
{
throw new ArgumentNullException("blockingCollection");
}
while (blockingCollection.Count > 0)
{
T item;
blockingCollection.TryTake(out item);
}
}
}
试用测试:
var cache = CacheManager<int>.GetInstance(10);
cache.UpdateDataEvent += Cache_UpdateDataEvent;
for (int i = 0; i < 100; i++)
{
cache.AddData(i);
Console.WriteLine($"Add:" + i);
}
Console.ReadKey();
async Task Cache_UpdateDataEvent(List<int> list)
{
await Task.Delay(TimeSpan.FromSeconds(1));
string tt = "";
foreach (var item in list)
tt += item + ",";
Console.WriteLine($"Inserted into database:" + tt);
}