用.NET创建定时缓存
发布时间:2006-10-14 3:05:48   收集提供:gaoqian

在先前的文章里,我详细讨论了.NET缓存的简单用法。不过那点用途还只能是这一话题的“开胃酒”。现在,我们更进一步,在那篇文章所开发的缓存基础上添加条目在缓存中的过期功能。

清单A的内容就是上次我们开发的有关代码。

新缓存及其改进
为了创建定时缓存,我们首先得确定某个对象是否已经到期了。出于这篇文章讨论范围,我假设用户能够确定准确的对象超期日期。这样,在将来还可以再改进为允许在缓存中存放的对象自己确定它们的过期日期。

我们首先修改insertCachedObject方法以支持缓存中对象的过期功能。当对象插入缓存时,该方法会给用户提供一个对象过期日期。这个日期参数被称为dtExpiration。insertCachedObject方法则存储经由该参数传递的过期日期。完成这一目标有若干方法。例如,我们可以创建一个封装对象,其中包含了日期和对象属性。如果这样做的话,insertCachedObject方法就会创建封装对象的实例并提供给它两个属性。我们选择相比更简单些的第二个方案:创建和声明第2个哈希散列表,并且指定它同第一个哈希散列表并行运行。这第二个哈希散列表叫做htExpiration。

对 insertCachedObject 方法的修改请见清单B。

现在,我们需要想办法发现过期的条目。在通过getCachedObject方法发出请求时,简单而且也是最偷懒的办法是检查每个缓冲对象。因为直到发出处理请求才调出过期对象,所以这是一种较差的算法。而且很有可能在时间上具有一定的不确定性。虽然这些到期对象驻留在缓存里,可是它们消费着珍贵的内存资源,这对应用程序的性能具有极其不利的影响。

不用这种偷懒的办法,我们创建一个后台线程反复检查htExpiration哈希表。

于是我们创建一个名叫TheReaperThread的类,这个类有一个方法负责建立无限循环。该类还被声明为内部类,因为它只用在CustomCache对象的上下文环境内。该类的全部代码请见清单C。

你得对清单C中的Shared Constructor注意了,它负责创建新的线程并启动该线程。我们接下来创建测试程序保证缓存运行正常。测试程序见清单D,该程序是一个控制台程序,当然也可以用.NET Framework 把以上程序当作项目来创建。


小结
定时缓存就这样完全实现了。虽然在功能上还比较简单,而且不能通知缓存的用户条目在什么时候到期,也不允许对象确定它们自己的过期算法。不过在这种简单缓存的基础上,将来我们还可以讨论开发新的功能,比如分派、内存管理等等。

Listing A


'Defines a Public Class called CustomCache
Public Class CustomCache
'Declares the hashtable use to store everything put into the cache.
'Since ht is shared there it follows a Singleton pattern
Private Shared ht As System.Collections.Hashtable
'A shared Constructor runs when the class is referenced very first time.
Shared Sub New()
'Instantiates the Hashtable
ht = New Hashtable()
End Sub
'A private constructor prevents clients instantiating CustomCache Object
Private Sub New()
End Sub
'Shared method used to put objects into the cache
Public Shared Sub insertCachedObject(ByVal key As Object, ByVal o As Object)
ht.Add(key, o)
End Sub
'Shared method used to retrieve objects from the cache
Public Shared Function getCachedObject(ByVal key As Object) As Object
Return ht.Item(key)
End Function
End Class


Listing B


Public Shared Sub insertCachedObject(ByVal key As Object, ByVal o As Object, ByVal dtExpiration As Date)
ht.Add(key, o)
'Add the expiration date to the second hashtable using the same key
htExpiration.Add(key, dtExpiration)
End Sub


Listing C


'Imports the Threading namespace - the ThreadStart object resides in it
Imports System.Threading
'Defines a Public Class called CustomCache
Public Class CustomCache
'Declares the hashtable use to store everything put into the cache.
'Since ht is shared there it follows a Singleton pattern
Private Shared ht As System.Collections.Hashtable
Private Shared htExpiration As System.Collections.Hashtable
'This class is responsible for finding expired objects and removing them from the cache
'this class is named TheReaperThread in remembrance of the Grim Reaper!
Class TheReaper
'This method will reap until the cows come home.
Sub ReapBabyReap()
Dim dt As Date
While True
'Output message to the console
Console.WriteLine("The reaper thread is looking for expired objects...")
'Declare an Enumerator - Enumerator are used to iterate collections
Dim myEnumerator As IDictionaryEnumerator
'Instantiate Enumerator
myEnumerator = htExpiration.GetEnumerator()
'Loops through all items
While myEnumerator.MoveNext()
'get the expiration date
dt = myEnumerator.Value
'Output to the console
Console.Write("Key: " + ControlChars.Tab + "{0}, Expires: " + ControlChars.Tab + "{1}", myEnumerator.Key, dt.ToString)
'Compare the date of expiration with the current date and time.
'If its less than or equal to zero in value then item gone
If Date.Compare(dt, Now()) <= 0 Then
'Output to the console
Console.WriteLine(" EXPIRED - Removing")

'Remove the expired item from the main hashtable
ht.Remove(myEnumerator.Key)
'don't forget to remove the expired item htExpiration.Remove(myEnumerator.Key)
' HACK Need to refresh the enumeration.
' This must be done to avoid the exception which occurs otherwise.
' This will cause the loop to restart from the beginning and may
' lead to certain items positioned
' at the end of the hashtable to remain longer than they would otherwise.
' This can be considered a hack and should be revisited
myEnumerator = htExpiration.GetEnumerator()
Else
'output to the console
Console.WriteLine(" Not Expired")
End If
'Give the reaper a break, he doesn't need to check every single second does he?
Thread.Sleep makes the current thread pause execution for specified number of
'milliseconds. This value could be set as a public property of the class.
Thread.Sleep(5000)
End While
End While
End Sub
End Class
'A shared Constructor runs when class referenced the first time.
Shared Sub New()
'Instantiates the Hashtable
ht = New Hashtable()
'Instantiates the Expiration Hashtable
htExpiration = New Hashtable()
'Declare and instantiate the TheReaperThread
Dim grimReaper As New TheReaper()
'Create a ThreadStart object, passing the address of grimReaper.ReapBabyReap.
'ThreadStart is a delegate! We will learn more about these in the next article.
Dim otter As New ThreadStart(AddressOf grimReaper.ReapBabyReap)
'Create a Thread object.
Dim oThread As New Thread(otter)
'Starting the thread invokes the ThreadStart delegate.
oThread.Start()
End Sub
'A private constructor prevents clients from instantiate the CustomCache Object
Private Sub New()
End Sub
'Shared method used to put objects into the cache
'dtExpiration is the date of expiration parameter
Public Shared Sub insertCachedObject(ByVal key As Object, ByVal o As Object, ByVal dtExpiration As Date)
ht.Add(key, o)
'Add the expiration date to the second hashtable using the same key
htExpiration.Add(key, dtExpiration)
End Sub
'Shared method used to retrieve objects from the cache
Public Shared Function getCachedObject(ByVal key As Object) As Object
Return ht.Item(key)
End Function
End Class


Listing D


Module TestCache
Sub Main()
'Creates a String which contains the Alphabet A..Z
Dim s As New String("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
'Inserts the String into the Cache using a string key of "Alphabet"
'Sets the expiration date to the current date and time plus one minute
UnitTest.CustomCache.insertCachedObject("Alphabet", s, Now().AddMinutes(1))
'Declares a Second String called s1
Dim s1 As String
'Retrieves the cache object which matches the key of "Alphabet"
s1 = UnitTest.CustomCache.getCachedObject("Alphabet")
'This should output the alphabet A..X
Console.WriteLine(s1)
'Don't hit the enter key until see item expired in the console window
Console.ReadLine()
'Declares a Third String called s1
Dim s3 As String
'Retrieves the cache object which matches the key of "Alphabet"
s3 = UnitTest.CustomCache.getCachedObject("Alphabet")
'This should output the alphabet A..X
Console.WriteLine(s3)
'By performing a Console.readline it allows us to see the
' output of the console window prior to its closing
Console.ReadLine()
End Sub
End Module

 

 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50