【问题标题】:Failing to programatically overwrite a file in an IIS Virtual Directory/Application (file is always locked)无法以编程方式覆盖 IIS 虚拟目录/应用程序中的文件(文件始终被锁定)
【发布时间】:2012-05-07 09:51:40
【问题描述】:

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

我的情况是这样的(在 Windows 2008 R2 服务器上):

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

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

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

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

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

Win32: How to get the process/thread that owns a mutex?

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

Using C#, how does one figure out what process locked a file?

^^ 这里的示例代码正是我所需要的,但不幸的是我无法让它工作。它总是抛出一个我无法弄清楚的“AccessViolationException”,因为示例代码广泛使用了 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 上传目录。 filepath是新上传的文件,targetfilepath是我的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);
        }

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

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

【问题讨论】:

  • I'm periodically trying to open the file, to see if the file is still being uploaded (or locked) - 您是否正确处理此流?另外,您的服务器代码是什么样的?
  • 编辑了我的问题并添加了一些相关的服务器代码。如果您需要更多信息,请告诉我。
  • 您是否 100% 确定锁定的是目标文件而不是源文件?
  • 你应该仅仅因为文件被锁定就杀死w3wp.exe进程。您应该找出为什么并解决它。也许某人当时只是在下载文件(而您将中止他们的下载)
  • 我会查看文件权限。您在没有确定性捕获的情况下捕获所有异常:IOExceptionUnauthorizedAccessExceptionFileNotFoundException。您是否以特殊身份运行您的工作进程? IIS 7 还是 6?上传文件的扩展名是什么(AFAIK,IIS 不锁定所有 mime 类型)?

标签: c# iis file-locking


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    大家都说……

    “做得更好”

    没有人说怎么做!!!

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

    每次都使用新的文件名。

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

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

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

    使用脚本对其进行前端处理。让客户端点击设置正确 HTTP 标头并具有“选择正确文件”逻辑的 ASPX,然后输出文件内容。这是一种非常流行的技巧,页面用于将存储在数据库中的图像写入浏览器,而 img 标签似乎是从文件中读取的。 (谷歌沿着这条线获取示例代码)。

    听起来像是 hack,其实不然。现代无锁内存缓存系统做类似的事情。不可能发生锁定或损坏;在“写入”完成之前,读者会看到旧版本。

    加上,这很简单,从脚本小子到穿孔卡片老手的每个人都会确切地知道你在做什么。去低科技!

    【讨论】:

    • 好吧,写完这些废话,我看到 qwerty13579 提到了类似的东西!你错过了吗?你从 12 小时前就已经在这里了,你似乎在让其他用户在这里打败死马。请考虑给他赏金并标记接受他的答案,以便我们其他人停止追逐红鲱鱼;-)
    【解决方案3】:

    您正在解决问题的症状,而不是解决根本原因。如果你想沿着这条路走,这里是杀死进程的代码http://www.codeproject.com/Articles/20284/My-TaskManager - 但更好的主意是正确地执行它并找出问题所在。我建议在 FileReadable 的 Catch Exception 中:

    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 日志文件并查看已断开客户端的状态并将状态代码与here 的状态代码进行比较。

    【讨论】:

    • 有人可以为我的理解解释否决票吗?恕我直言,第三点是找到解决方案的关键......
    • +1 :这个答案不值得投反对票:最好的办法是正确地解决问题并找出问题所在。杀死工作进程以解锁文件?我不得不说 WTF :) 这不是好方法。
    【解决方案4】:

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

    【讨论】:

    • 使用 (FileStream fs = File.Open...) :)
    • 啊,不错。我个人不喜欢依赖它。为了平衡和理智,如果我看到打开,我喜欢看到关闭......我之前让 GC 处理这类事情让我很受折磨。
    • using 语句有助于确定性终结,即使我个人在我的 finally 块中有例如:connection.Close()(只是为了让我保持理智) - 它不需要什么时候使用 - 使用。
    【解决方案5】:

    重启IIS可以解锁w3wp.exe获取的文件。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-04-07
      • 1970-01-01
      • 2010-10-08
      • 1970-01-01
      • 1970-01-01
      • 2013-02-15
      • 1970-01-01
      相关资源
      最近更新 更多