2

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

我的场景是这样的(在 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# 的最值得注意的问题:

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

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

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

^^ 这里的示例代码正是我所需要的,但不幸的是我无法让它工作。它总是抛出一个我无法弄清楚的“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 资源管理器手动删除/重命名/移动文件而没有任何问题。为什么这行得通,为什么我的应用程序会出现“被另一个进程锁定”异常?

4

5 回答 5

3

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

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

于 2012-05-10T15:52:43.970 回答
3

大家说...

“做一个更好的方法”

没人说怎么做!!!

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

每次只需使用新的文件名。

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

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

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

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

听起来像一个黑客,它不是。现代无锁内存缓存系统做类似的事情。不可能发生锁定或损坏;直到“写”完成,读者才能看到旧版本。

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

于 2012-05-14T20:37:42.330 回答
0

您正在解决问题的症状而不是解决根本原因。如果你想沿着这条路走下去,这里是杀死进程的代码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 日志文件并查看已断开客户端的状态并将状态代码与此处的状态代码进行比较。
于 2012-05-12T03:39:18.747 回答
0

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

于 2012-05-14T15:17:11.187 回答
0

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

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

于 2013-08-28T11:21:17.893 回答