我可以提供最简单的解释:
在我的.NET1.1网络应用程序中,我在光盘上创建一个文件,在Render方法中,并将一个项目添加到缓存中,例如,在一分钟内到期。我还有一个回调方法,在缓存项到期时调用,删除Render创建的文件。在Page_Init方法中,我尝试访问Render方法写入光盘的文件。这两个方法都有一个lock语句,锁定一个私有静态Object。
意向:
创建一个基本上将自己的副本写入光盘的页面,该光盘在变得太旧(或过时,内容过时)之前被删除,而在光盘上存在的情况下提供该文件。
观察到的问题:
我认为这确实是两个问题。请求页面执行我所期望的操作,它将页面呈现为光盘并立即提供服务,同时将有效期限项目添加到缓存中。测试到期时间为1分钟。
然后我希望60秒后调用回调方法并删除文件。它没有。
再过一分钟(为了争论)我在浏览器中刷新页面。然后我可以看到调用回调方法并锁定锁对象。 Page_Init也会被调用并锁定同一个对象。但是,这两种方法似乎都会进入锁定代码块并继续执行。
这导致:渲染检查文件在那里,回调方法删除文件,渲染方法尝试服务now-deleted-file。
可怕的简化代码提取:
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 :(得分:1)
不确定为什么你的解决方案不起作用,但考虑到后果,这可能是一件好事......
我建议采用完全不同的路线。将管理文件的过程与请求文件的过程分开。
请求只需转到缓存,获取文件的完整路径,然后将其发送给客户端。
另一个进程(未绑定到请求)负责创建和更新文件。它只是在首次使用/访问时创建文件,并将完整路径存储在缓存中(设置为永不过期)。在定期/适当的时间间隔,它使用不同的随机名称重新创建文件 ,在缓存中设置此新路径,然后删除旧文件(注意它不被锁定另一个要求)。
您可以使用线程或ThreadPool在应用程序启动时生成此文件管理进程。链接文件管理和请求将始终导致问题,因为您的进程将同时运行,要求您执行一些最好避免的线程同步。
答案 1 :(得分:0)
我要做的第一件事就是打开Threads窗口,观察运行Page_Init的线程以及Call Back正在运行的线程。我知道两种方法可以锁定同一对象的唯一方法是它们是否在同一个线程中运行。
修改强>
这里真正的问题是Server.Transfer实际上是如何工作的。 Server.Transfer只是配置一些ASP.NET内部细节,指示请求将要传输到服务器上的其他URL。然后它调用Response.End,然后抛出一个ThreadAbortException。当时没有读取或发送给客户的实际数据。
现在,当异常发生时,代码执行会通过锁保护代码块。此时回叫功能可以获取锁定并删除文件。
现在在ASP.NET内部某处,ThreadAbortException以某种方式处理,并处理对新URL的请求。此时它发现文件已丢失。