无法以编程方式覆盖 IIS 虚拟目录/应用程序中的文件(文件始终处于锁定状态)

本文关键字:文件 于锁定 状态 锁定 应用程序 方式 编程 覆盖 IIS 虚拟 | 更新日期: 2023-09-27 18:33:26

起初我以为我面临着一个非常简单的任务。但是现在我意识到它并不像我想象的那样有效,所以现在我希望你们能帮助我,因为我现在几乎被困住了。

我的场景是这样的(在Windows 2008 R2服务器上(:

  1. 一个文件每天上传到 FTP 目录 3 次。文件名始终相同,这意味着现有文件每次都会被覆盖。
  2. 我已经编写了一个简单的 C# 服务,该服务正在监视 FTP 上传目录,我为此使用了 FileSystemWatcher 类。
  3. 上传文件需要几分钟,因此一旦文件观察器注册了更改,我就会定期尝试打开文件,以查看文件是否仍在上传(或锁定(
  4. 一旦文件不再锁定,我尝试将文件移动到我的 IIS 虚拟目录。我必须先删除旧文件,然后再移动新文件。这就是我的问题开始的地方。该文件似乎总是被 IIS(w3wp.exe进程(锁定。

经过一些研究,我发现我必须杀死锁定文件的进程(在这种情况下为 w3wp.exe(。为此,我创建了一个新的应用程序池,并将虚拟目录转换为应用程序。现在我的目录在一个单独的 w3wp.exe 进程下运行,我应该可以安全地杀死并将新文件移动到那里。

现在我只需要找到合适的 w3wp.exe 进程(总共有 3 个 w3wp.exe 进程在运行,每个进程都在单独的应用程序池下运行(,它对我的目标文件有锁定。但这在 C# 中似乎几乎是不可能的任务。我在SO上发现了许多关于"查找锁定特定文件的过程"的问题,但没有一个答案对我有帮助。例如,进程资源管理器准确地告诉我哪个进程正在锁定我的文件。

我不明白的下一件事是,我可以通过Windows资源管理器删除目标文件而没有任何问题。只有我的 C# 应用程序收到"文件正被另一个进程使用"错误。我想知道这里有什么区别...

以下是有关锁定文件和 C# 的 SO 最值得注意的问题:

Win32:如何获取拥有互斥锁的进程/线程?

^^此处的示例代码确实有效,但这会输出每个活动进程的打开句柄 ID。我只是不知道如何搜索特定的文件名,或者至少将句柄 ID 解析为文件名。这个WinAPI的东西远远超出了我的头顶。

使用 C#,如何确定哪个进程锁定了文件?

^^这里的示例代码正是我所需要的,但不幸的是我无法让它工作。它总是抛出一个我无法弄清楚的"访问违规异常",因为示例代码广泛使用了 WinAPI 调用。

简单的任务,不可能做到?我感谢任何帮助。

编辑以下是我的服务器代码的一些相关部分:

用于检测文件是否被锁定的辅助函数:

    private bool FileReadable(string file, int timeOutSeconds)
    {
        DateTime timeOut = DateTime.Now.AddSeconds(timeOutSeconds);
        while (DateTime.Now < timeOut)
        {
            try
            {
                if (File.Exists(file))
                {
                    using (FileStream fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None))
                    {
                        return true;
                    }
                }
                return false;
            }
            catch (Exception)
            {
                Thread.Sleep(500);
            }
        }
        m_log.LogLogic(0, "FileReadable", "Timeout after [{0}] seconds trying to open the file {1}", timeOutSeconds, file);
        return false;
    }

这是我的 FileSystemWatcher 事件中的代码,它正在监视 FTP 上传目录。 文件路径是新上传的文件,目标文件路径是我的 IIS 目录中的目标文件。

        // here I'm waiting for the newly uploaded file to be ready
        if (FileReadable(filepath, FWConfig.TimeOut))
        {
            // move uploaded file to IIS virtual directory
            string targetfilepath = Path.Combine(FWConfig.TargetPath, FWConfig.TargetFileName);
            if(File.Exists(targetfilepath))
            {
                m_log.LogLogic(4, "ProcessFile", "Trying to delete old file first: [{0}]", targetfilepath);
                // targetfilepath is the full path to my file in my IIS directory
                // always fails because file is always locked my w3wp.exe :-(
                if(FileReadable(targetfilepath, FWConfig.TimeOut))
                    File.Delete(targetfilepath);
            }
            File.Move(filepath, targetfilepath);
        }

编辑2:在客户端下载文件时杀死 w3wp.exe 进程对我们来说没有问题。我只是很难找到锁定文件的正确 w3wp.exe 进程。

此外,我的客户端应用程序正在客户端上下载文件,正在检查上次修改日期的 HTTP HEAD。客户端每 10 分钟检查一次日期。因此,该文件可能被 IIS 锁定,因为有客户端不断检查该文件的 HTTP HEAD。尽管如此,我不明白为什么我可以通过Windows资源管理器手动删除/重命名/移动文件而没有任何问题。为什么会这样做,为什么我的应用程序会出现"被另一个进程锁定"异常?

无法以编程方式覆盖 IIS 虚拟目录/应用程序中的文件(文件始终处于锁定状态)

我遇到的一个问题是文件在写入时存在,这意味着它也会被锁定。 如果此时调用了 FileReadable(( 函数,它将返回 false。

我的解决方案是,在写入文件的进程中,将文件写入OUTPUT1.TXT,然后在完全写入并且FileStream关闭后,将其重命名为OUTPUT2.TXT。 这样,OUTPUT2.TXT的存在表明文件已写入并(希望(解锁。 只需检查 FileReadable(( 循环中的OUTPUT2.TXT。

每个人都说...

"做得更好">

没有人说怎么做!!

方法如下。因为您提到了"我的客户端应用程序",所以这里有一个关键的机会,如果您无法控制读取文件的应用程序,您将没有机会。

只需每次都使用新文件名即可。

您可以控制程序读取和写入文件。在文件名中放一个递增的#,让客户端选择最大的#(实际上是最新的日期,然后你的数字可以环绕(。如果可以,让编写器程序清理旧文件;如果没有,他们不会伤害任何东西。IIS最终会放手。如果没有,只需每周打开资源管理器并自己动手!

使这项工作的其他关键是更新频率低(文件不会堆积得太糟糕(,以及FTP + Web服务器位于同一驱动器上的事实(否则MOVE不是原子的,客户端可能会获得半复制的文件。如果FTP驱动器不同,解决方案是复制到Web服务器上的临时驱动器然后移动(。

但是,如果您无法更改客户端或它必须只读取一个名称怎么办?

用脚本前端它。让客户端命中设置正确的 HTTP 标头并具有"选择正确的文件"逻辑的 ASPX,并吐出文件内容。这是一种非常流行的技巧页面,用于将存储在数据库中的图像写入浏览器,而 img 标签似乎从文件中读取。(沿着这条线谷歌搜索示例代码(。

听起来像是黑客,但事实并非如此。 现代无锁内存缓存系统也做了类似的事情。锁或损坏是不可能发生的;在"写入"完成之前,读者会看到旧版本。

另外,这很简单,从脚本小子到穿孔卡兽医,每个人都会确切地知道你在做什么。去低技术!

您正在对问题的症状进行故障排除,而不是对根本原因的修复。如果你想沿着这条路走下去,这里有杀死进程 http://www.codeproject.com/Articles/20284/My-TaskManager 的代码 - 但更好的主意是正确地做到这一点并找出问题所在。我在FileReadable的捕获异常中建议:

catch (Exception ex) {
if (ex is IOException && IsFileLocked(ex)) {
//Confirm the code see's it as a FileLocked issue, not some other exception
//its not safe to unlock files used by other processes, because the other process is likely reading/writing it. 
}
}
private static bool IsFileLocked(Exception exception)
{
    int errorCode = Marshal.GetHRForException(exception) & ((1 << 16) - 1);
    return errorCode == 32 || errorCode == 33;
}
  1. 关闭所有防病毒软件并重新测试
  2. 增加轮询超时持续时间以查看它是否只是一个计时问题
  3. 检查 FTP 日志文件并查看断开连接的客户端的状态,并将状态代码与此处的状态代码进行比较。

我在您的示例代码中没有看到您正在关闭文件流的位置。保持文件流打开将锁定文件。关闭流是个好主意。你可能不想杀死你的w3wp.exe进程,正如这里的其他人所提到的。

重新启动

IIS可以解锁w3wp.exe拍摄的文件。

cmd (以管理员身份运行( -> iisreset/stop -> 更新/删除文件 Windows 资源管理器 -> IISRESET/Start