【问题标题】:FileStream.Close() is not closing the file handle instantlyFileStream.Close() 不会立即关闭文件句柄
【发布时间】:2015-08-10 17:48:37
【问题描述】:

我正在分块读取源文件并将其传递给 WCf 服务以在某个远程 SMB 上写下来。我一直打开 FileStream 直到所有数据都被写入。

多次打开和关闭文件句柄会降低性能,所以我采用这种方法。

所有数据写入后,我调用 CloseHandle()。然后我可能需要通过调用 DoSomeOperation() 对同一个文件执行一些其他操作。因为我在 CloseHandle() 函数中关闭了文件句柄,但在 DoSomeOperation() 中出现错误“文件正在与其他进程一起使用”。如果我在延迟一段时间后调用 DoSomeOperation() 则问题不存在。

请帮助我们在我调用 FileStream.Close() 时立即关闭文件句柄。

这段代码sn-p是一个大程序的一部分,这里就不一一列举了。

//In WCF service
FileStream fs = null;
public void AppendBytes(string fileName, byte[] data, long position)
{
    try
    {
        if (fs==null)//In first call, open the file handle
            fs = System.IO.File.Open(fileName, System.IO.FileMode.Append, System.IO.FileAccess.Write, System.IO.FileShare.None);

        fs.Write(data, 0, data.Length);
    }
    catch (Exception ex)
    {
        //Close handle in case of error
        if (fs != null)
            fs.Close();
    }
}

public void CloseHandle()
{
    //Close handle explicitly
    if (fs != null)
        fs.Close();
}

public void DoSomeOperation(string fileName)
{
    using (FileStream fsO = System.IO.File.Open(fileName, System.IO.FileMode.Append, System.IO.FileAccess.Write, System.IO.FileShare.None))
    {
        //Do something with file here, this is atomic operation so I am opening FileStream with 'using' to dispose at operation is over
    }
}

//In client
public void CallerFunction()
{
    //Read Data from sourceFile in chunk and copy to target file using WCF.AppendBytes on another machine
    WCF.AppendBytes(filename, data, pos);
    WCF.CloseHandle();
    WCF.DoSomeOperation(filename); //I get error here that file is in use with some other process. if I put a thread.sleep(1000) just before this statement then all works fine.
}

我编写了一个小测试代码来在控制台应用程序上重现相同的场景:只需从 Main() 调用 TestHandleClose(),它会在一些循环后报错。

 static  void TestHandleClose()
        {
            int i = 0;
            try
            {

                if (File.Exists(@"d:\destination\file2.exe"))
                    File.Delete(@"d:\destination\file2.exe");

                byte[] data = null;
                int blocksize = 10 * 1024 * 1024;

                for( i=0;i<100;i++)
                {
                    using (FileStream fr = File.Open(@"d:\destination\File1.zip", FileMode.Open, FileAccess.Read, FileShare.None))
                    {
                        data = new byte[blocksize];
                        fr.Read(data, 0, blocksize); //We are reading the file single time but appending same data to target file multiple time.

                        using (FileStream f = File.Open(@"d:\destination\file2.exe", FileMode.Append, FileAccess.Write, FileShare.None))
                        {
                            f.Write(data, 0, data.Length); //We are writing same data multiple times.
                            f.Flush();
                            f.Close();
                        }
                    }

                }

                if (File.Exists(@"d:\destination\file2.exe"))
                    File.Delete(@"d:\destination\file2.exe");

            }
            catch (Exception ex)
            {
                throw;
            }
        }

【问题讨论】:

  • 尝试在所有 fs.Close() 方法之前添加 fs.Flush()。让我们知道这是否有效。
  • 如果您有防病毒软件,您可以尝试禁用它以进行测试吗?
  • 我立即怀疑这样的代码,其中fs 是一个共享变量。例如。如果在同一实例上有两个同时调用AppendBytes,他们可能都观察到fs 为空,都打开一个新的Stream,都竞争设置fs,并且您泄漏一个流直到它的垃圾被收集.这是否可能在这里很难说,并且取决于您的实例化/线程选项。
  • 您是否尝试禁用防病毒软件?
  • 嗯,关于 WCF 的事情是,对于每个客户端调用,通常都会创建一个新的服务实例。您确定将 WCF 配置为使用静态实例吗?在任何其他情况下,CloseHandle 将不执行任何操作,因为 fs 为 null,并且该文件将从第一个服务实例开始保持打开状态,因为它永远不会关闭。

标签: c# .net


【解决方案1】:

最终的示例代码有所帮助,我很确定我遇到了您的问题。

问题是,即使是最新的示例代码也无法重现。但是,它显示了您可能错过的另一件事 - 您正在编写的文件是 .exe。为什么这是个问题?嗯,有几个原因,但其中之一是当您列出 .exe 文件正在使用资源管理器的目录时,资源管理器会继续并尝试读取它(以获取图标)。在这么短的时间内,无法使用FileShare.None 打开文件(事实上,FileShare.Read 可能也无济于事,因为很可能打开它的人没有指定FileShare.ReadWrite)。

再一次,FileStream 关闭得很好,并且运行良好(尽管摆脱了 FlushClose 调用 - 它们在浪费性能,而且毫无用处)。问题是另一个进程同时尝试读取该文件。它可能是一些文件管理器,比如我的情况(Explorer、Total Commander、...),它可能是一些 FileSystemWatcher 你在某个地方,它可能是一个病毒扫描程序(尽管现在大多数病毒扫描程序都使用卷影副本),它可能是自动为图像等创建缩略图的东西。但是您自己发布的代码根本不会导致问题 - 是其他人抓取了您的文件。

基本上有两个选项 - 要么在需要的时候一直打开文件,要么将 IOException 视为临时文件,并在给定的时间间隔内重试几次。无论如何,这就是你应该做的,而不是依赖于快乐的路径 - 大多数读者只允许并发读取,而不是写入。

【讨论】:

  • 你太棒了,它就像奇迹一样工作。问题仅在于扩展名 .exe。我将 Flush()、Close() 放在 using 块中以满足其他开发人员的需求。他们不断提出这些想法:)。因此,如果出现 IO 异常,我将尝试使用 Thread.Sleep(50) 在循环中访问相同的文件句柄最多 1 秒,然后再声明该文件正在被其他进程使用。
  • @Romil 是的,Flush 几乎肯定会让你的表现变得更糟 - 自动调用为 usingDispose 超出范围将负责冲洗和关闭就好了。当您Flush 时,您基本上是在说“扔掉所有那些使一切工作快速的缓冲区”(它甚至不能确保数据被刷新,因为它实际上是异步的,而不是阻塞的)。在关闭文件之前Flush 一次并没有什么不同,但它是不必要的并且可能令人困惑:)
【解决方案2】:

在我的情况下,缺少文件共享模式。我曾经在短时间内从同一个文件中的多个源读取/写入。有时它顺利通过,有时它被阻塞(另一个进程正在保存文件),即使文件流被释放并且我正在使用锁和 using()。当我添加 FileShare 模式 ReadWrite 时,一切都解决了。


using (FileStream fileStream = new FileStream(file, mode, access, FileShare.ReadWrite))
{...

【讨论】:

  • 请阅读滦安的回答。这是一个完全不同的场景。
猜你喜欢
  • 1970-01-01
  • 2014-01-18
  • 1970-01-01
  • 2020-12-05
  • 1970-01-01
  • 1970-01-01
  • 2020-11-08
  • 1970-01-01
  • 2021-11-29
相关资源
最近更新 更多