2009-10-29 23 views
1

简单的解释,我可以生产:锁定在缓存项的回调和其他方法使用似乎并没有锁定

在我的.NET1.1的Web应用程序创建磁盘上的一个文件,在Render方法,并添加一个项目到缓存过期,比如一分钟。我也有一个回调方法,当缓存项到期时被调用,这会删除由Render创建的文件。在Page_Init方法中,我尝试访问Render方法写入光盘的文件。这两个方法都有一个锁定语句,锁定一个私有静态对象。

意向:

要创建一个网页,其中主要以光盘写入其自身的副本,它被删除之前,它变得太旧(或过期,内容明智的),而服务的文件,如果存在在光盘上。

问题观察:

这实际上是两个问题,我想。请求页面做我期望的事情,它会将页面渲染并立即提供,同时将缓存中的过期条目添加到缓存中。为了测试到期时间是1分钟。

然后我期待60秒后调用回调方法并删除文件。它没有。

再过一分钟(为了参数)我刷新浏览器中的页面。然后我可以看到调用的回调方法,并在锁对象上放置一个锁。 Page_Init也会被调用并在同一个对象上放置一个锁。但是,这两种方法似乎都进入了它们的锁码区块并继续执行。

这导致:渲染检查文件存在,回调方法删除文件,渲染方法尝试服务于现在删除的文件。

可怕地简化代码片段:

public class MyPage : Page 
{ 
    private static Object lockObject = new Obect(); 

    protected void Page_Init(...) 
    { 
    if (File.Exists(...)) 
    { 
     lock (lockObject) 
     { 
     if (File.Exists(...)) 
     { 
      Server.Transfer(...); 
     } 
     } 
    } 
    } 

    protected override void Render(...) 
    { 
    If (!File.Exists(...)) 
    { 
     // write file out and serve initial copy from memory 
     Cache.Add(..., new CacheItemRemovedCallback(DoCacheItemRemovedCallback)); 
    } 
    } 

    private static void DoCacheItemRemovedCallback(...) 
    { 
    lock (lockObject) 
    { 
     If (File.Exists(...)) 
     File.Delete(...); 
    } 
    } 
} 

任何人都可以解释这一点,好吗?我知道回调方法本质上是懒惰的,因此只有在发出请求后才会回调,但.NET1.1中的线程当然不会让两个lock()块同时进入?

谢谢,

马特。

+0

不相关,但:在网页上的'private static Object lockObject = new Obect()'?这意味着*所有*请求共享一个锁 - 这是你的意图? – 2009-10-29 12:46:36

+0

也许我没有正确理解这一点,但好像你重复了OutputCache指令的功能?见http://msdn.microsoft.com/en-us/library/hdxfb6cy(VS.71).aspx – PhilPursglove 2009-10-29 13:09:15

+0

[尴尬沉默] 是的,它会出现这样。实际上,我的印象是.NET1.1没有这个功能。我现在要扼杀我的大脑,并试图回忆为什么会这样,以及是否存在1.1 OutputCache中的缺陷。然后我将使用它。 谢谢。 [更多寂静] – 2009-10-29 16:30:09

回答

1

不知道为什么你的解决方案不起作用,但是这可能是一件好事,考虑后果......

我会建议一个完全不同的路线。将管理文件的过程与请求文件的过程分开。

请求只应该进入缓存,获取文件的完整路径,并将其发送到客户端。

另一个进程(不绑定请求)负责创建和更新文件。它只是在首次使用/访问时创建文件并将完整路径存储在缓存中(设置为永不过期)。以正常/适当的时间间隔,使用不同的随机名称重新创建文件,在缓存中设置此新路径,然后删除旧文件(注意未被另一个请求锁定)。

您可以使用线程或ThreadPool在应用程序启动时产生此文件管理进程。将您的文件管理和请求链接起来总是会导致问题,因为您的进程将同时运行,因此需要您执行一些最好避免的线程同步。

+0

我最终做的并不是删除缓存过期时的文件,而是将缓存完全排除在外 - 只需检查LastWrite和LastModified标记即可。这要求我在用新版本覆盖文件时重新加盖它们,因为我没有观察到它们在资源管理器中的时间戳发生了任何变化,或者以编程方式检查了之前被覆盖的文件。 – 2009-11-04 15:19:07

0

我要做的第一件事是打开Threads窗口,观察Page_Init正在运行哪个线程以及哪个线程正在运行回调。我知道两种方法可以在同一个对象上放置一个锁的唯一方法就是它们在同一个线程中运行。

编辑

这里真正的问题是如何Server.Transfer的实际工作。 Server.Transfer只是简单地配置一些ASP.NET内部细节,指出请求即将传输到服务器上的其他URL。然后它调用Response.End,然后抛出一个ThreadAbortException。当时没有实际的数据被读取或发送给客户端。

现在,当发生异常时,代码执行会通过锁定保留代码块。此时回拨功能可以获取锁定并删除文件。

现在深入ASP内部。NET以某种方式处理ThreadAbortException,并处理对新URL的请求。此时它发现文件已经丢失。

+0

好的,感谢您的建议 - 我并没有意识到有线程窗口! 无论如何,我看到你是对的,并且有两个线程可以在锁定一个静态对象时输入自己的锁语句块。 然而,据我了解,这与微软说它应该做的事情背道而驰。除非我对“线程”这个术语的使用(可能)有所困惑:http://msdn.microsoft.com/en-us/library/c5kehkcz(VS.71).aspx – 2009-10-29 15:38:03

+0

你不会感到困惑。两个不同的线程不能同时锁定同一个对象。期。其他事情正在发生。将'readonly'添加到lockObject中,以便它不能被替换。如何准确地观察这种行为? – AnthonyWJones 2009-10-29 16:18:32

+0

我只是单步执行代码。 外在的观察是,当我尝试Server.Transfer到它时,明显缓存在光盘上的页面会发出异常。它应该在那里,因为在我转移到它之前,我检查它是否存在。然而,当转移发生时,它就消失了,导致异常。 该问题的内部视图显示两个锁定语句正在同时执行。当一个人从缓存中请求一个对象时,他们会领先于另一个。我会认为这是其他线程可以领先的地方,因为它没有进行缓慢的框架调用。 – 2009-10-29 16:27:05