在进行I/O操作(如文件读取、数据库查询等)时,如果结果在一段时间内不会发生变化,但每次检索都需要花费一定的时间,那么有没有一种方法可以一次性完成I/O操作,并将数据存储一段时间,以便下次需要时可以立即获取?这就是缓存技术的核心思想。缓存技术可以减少对数据库的查询次数,提高系统的响应速度和性能。
TypeCache是一个.NET类,它使用静态字段来缓存特定数据类型的值。它的设计动机是为了创建一个缓存系统,用于缓存值对象(Value Objects,简称VO),这类对象通常只包含属性和字段,并且数据通常来自数据库。例如,用户账户信息就是经常被请求的数据之一。由于用户权限不经常变化,因此没有必要每次都去数据库查询,而是可以将用户权限信息保存在缓存系统中,并定期更新。
在深入了解TypeCache类之前,需要理解两个概念:静态字段和泛型。静态字段是类字段,其值可以被该类型的任何实例读取。例如:
class TestStatic {
private static int _count = 0;
public void Add() { _count++; }
public int GetCount() { return _count; }
}
class Program {
static void Main(string[] args) {
TestStatic t1 = new TestStatic();
TestStatic t2 = new TestStatic();
Console.WriteLine("t1 Count:{0} - t2 Count:{1}", t1.GetCount(), t2.GetCount());
t1.Add();
Console.WriteLine("t1 Count:{0} - t2 Count:{1}", t1.GetCount(), t2.GetCount());
t2.Add();
Console.WriteLine("t1 Count:{0} - t2 Count:{1}", t1.GetCount(), t2.GetCount());
}
}
输出结果为:
t1 Count:0 - t2 Count:0
t1 Count:1 - t2 Count:1
t1 Count:2 - t2 Count:2
泛型是.NET中的一种类型参数化机制,允许创建可重用的类和方法。在TypeCache中,泛型用于实现对不同数据类型的缓存。
TypeCache的使用非常简单,只需要通过Get和Set方法来获取和设置缓存值。例如:
string valstr = TypeCache.Get("key.str1");
if (valstr == null) {
valstr = GetStringFromExpensiveIO();
TypeCache.Set("key.str1", valstr);
}
首先尝试从缓存中检索值,如果不存在,则执行耗时的I/O操作,并将结果存储回缓存。
可以通过设置ExpirationInteval属性来改变缓存项的有效时间。例如:
TypeCache.ExpirationInteval = 2000; // 两秒
同样,可以通过设置CacheCleanInteval属性来改变缓存清理服务的运行间隔。
TypeCache.CacheCleanInteval = 10000; // 十秒
TypeCache类不使用委托或事件来在缓存值过期时重新加载缓存值,以保持类的使用尽可能简单。
缓存系统需要定期清理内存。TypeCache使用一个单例类(TypeCacheCleaner),它包含一个委托集合(每个泛型类型T一个),并创建一个唯一的线程来执行这些委托。每个委托都是对缓存中每种类型的缓存清理器的调用。这个线程仍然遵循CacheCleanInteval属性,但其定时器运行速度取决于它能找到的最低CacheCleanInteval。
为了保持类的使用简单,TypeCache类被设计为线程安全。任何尝试读取或写入内部缓存集合的操作都被锁包围。在第一个版本中,使用了System.Threading命名空间中的ReaderWriterLock,但经过研究后发现这个类不再推荐使用(甚至CLR团队在多线程方面也遇到了问题,并且在.NET 3.5中已经有一个替代品ReaderWriterLockSlim)。由于这个类旨在在.NET 2.0上运行,因此使用了旧的且可靠的lock(System.Threading.Monitor)。测试表明,ReaderWriterLock比传统的lock更快,但作者选择了可靠性而不是性能。