使用using语句块会关闭这个数据流吗?

34

我似乎养成了一个不良编码习惯。下面是我写的一段代码示例:

using(StreamReader sr = new StreamReader(File.Open("somefile.txt", FileMode.Open)))
{
    //read file
}
File.Move("somefile.txt", "somefile.bak"); //can't move, get exception that I the file is open

我曾认为由于using语句在StreamReader上显式调用了Close()Dispose(),所以FileStream也会被关闭。

但是我遇到的问题只能通过将上述代码块更改为以下内容才得以解决:

using(FileStream fs = File.Open("somefile.txt", FileMode.Open))
{
  using(StreamReader sr = new StreamReader(fs))
  {
    //read file
  }
}

File.Move("somefile.txt", "somefile.bak"); // can move file with no errors

在第一个块中通过处理关闭StreamReader是否也会关闭底层的FileStream?或者,我错了吗?

编辑

我决定发布实际有问题的代码块,看看我们能否找到原因。现在只是好奇。

我认为在using子句中有问题,所以我将所有内容都展开了,但每次仍然无法复制。我在此方法调用中创建了文件,因此我不认为其他任何东西都在该文件上打开了句柄。我还验证了从Path.Combine调用返回的字符串是否正确。

private static void GenerateFiles(List<Credit> credits)
{
    Account i;
    string creditFile = Path.Combine(Settings.CreditLocalPath, DateTime.Now.ToString("MMddyy-hhmmss") + ".credits");

    StreamWriter creditsFile = new StreamWriter(File.Open(creditFile, FileMode.Create));

    creditsFile.WriteLine("code\inc");

    foreach (Credit c in credits)
    {
        if (DataAccessLayer.AccountExists(i))
        {
            string tpsAuth = DataAccessLayer.GetAuthCode(i.Pin);
            creditsFile.WriteLine(String.Format("{0}{1}\t{2:0.00}", i.AuthCode, i.Pin, c.CreditAmount));
        }
        else
        {
            c.Error = true;
            c.ErrorMessage = "NO ACCOUNT";
        }

        DataAccessLayer.AddCredit(c);

    }

    creditsFile.Close();
    creditsFile.Dispose();

    string dest =  Path.Combine(Settings.CreditArchivePath, Path.GetFileName(creditFile));
    File.Move(creditFile,dest);
    //File.Delete(errorFile);
}

有可能其他应用程序已经打开并锁定了该文件吗?您也可以检查一下。 - Daniel
DateTime.Now.ToString("MMddyy-hhmmss")。你每秒钟创建了多少个文件? - Dave
@Dave,一天一次。这个函数每天只会被调用一次。 - scottm
2
检查防病毒扫描器、索引服务器等。 - Dominic Cronin
5个回答

42

是的,StreamReader.Dispose 会关闭底层流(对于所有公共创建方式)。不过,有一种更好的选择:

using (TextReader reader = File.OpenText("file.txt"))
{
}

这样做的额外好处是,它使用一种提示Windows你将按顺序访问它的方式打开底层流。

以下是一个测试应用程序,展示了第一个版本在我的机器上可以正常工作。我并不是要说这特别有什么证明——但我很想知道它对你来说工作得如何。

using System;
using System.IO;

class Program
{
    public static void Main(string[] args)
    {
        for (int i=0; i < 1000; i++)
        {
            using(StreamReader sr = new StreamReader
                  (File.Open("somefile.txt", FileMode.Open)))
            {
                Console.WriteLine(sr.ReadLine());
            }
            File.Move("somefile.txt", "somefile.bak");
            File.Move("somefile.bak", "somefile.txt");
        }
    }
}

如果这有效,那就说明与读取时所做的事情有关......

现在这是您编辑过的问题代码的缩短版 - 对我来说,即使在网络共享上也可以正常工作。请注意,我已将 FileMode.Create 更改为 FileMode.CreateNew - 否则可能仍然有一个应用程序使用旧文件的句柄。这对您有用吗?

<code><code>using System;
using System.IO;

public class Test
{    
    static void Main()
    {
        StreamWriter creditsFile = new StreamWriter(File.Open("test.txt", 
                                          FileMode.CreateNew));

        creditsFile.WriteLine("code\\inc");

        creditsFile.Close();
        creditsFile.Dispose();

        File.Move("test.txt", "test2.txt");
    }
}
</code></code>

为什么我不能移动第一个块中的文件呢? - scottm
我以前已经用第一种方式编码了一百次,从来没有遇到过问题。现在我正在打开一个Windows共享文件,这是我能解决问题的唯一方法。第一个示例每次都失败,而第二个示例每次都成功。所以我认为我做错了。 - scottm
刚刚尝试了一下对网络共享进行操作(虽然共享是一个Linux盒子),然后它就正常工作了。唯一的区别在于,在某些情况下,流先被关闭,然后才是StreamReader。但是StreamReader.Dispose肯定会调用Stream.Close。非常奇怪。 - Jon Skeet
我编辑了问题,并添加了实际的代码块,其中出现了这种情况(所有名称都已更改以保护无辜者)。也许有人可以揭示我的逻辑缺陷。 - scottm
+1. @scotty dispose 调用内部 stream.Close,进而调用其 Dispose。在 JS 对我的答案进行评论后,我使用反编译工具进行了检查(我已将其删除,因为它是错误/误导的 :()。 - eglasius
显示剩余12条评论

12
注意——你的使用块不需要嵌套在它们自己的块中,它们可以是顺序的,例如:
using(FileStream fs = File.Open("somefile.txt", FileMode.Open))
using(StreamReader sr = new StreamReader(fs))
{
    //read file
}

在这种情况下,释放顺序仍然与嵌套块相同(即,在此情况下,StreamReader仍将在FileStream之前被释放)。


8
在我看来,另一种方式更好。 - scottm
3
理解这与其他具有初始子句的结构没有区别,该结构由语句组成,其中语句可以是一个代码块。(一个经典的例子是if(list!= null)foreach(object item in list){...},其中foreach嵌套在if中。)具体而言,这就是"嵌套" - 一种不理想的缩进风格,因为它没有指示嵌套层次。如果你对除第一行之外的所有行进行缩进,则可以更清楚地看出:第二个“using”嵌套在第一个“using”内部。 - ToolmakerSteve

1

我建议您尝试使用FileInfo.Open()FileInfo.MoveTo()代替File.Open()File.Move()。您也可以尝试使用FileInfo.OpenText()。但这只是建议。


我会尝试使用FileInfo方法,但我认为在底层,它们只是对File.Move的调用。 - scottm
是的,但也许它们在内部处理流(由FileInfo.Open()创建)。此外,文档表示它们仅执行一次某些安全检查,因此可能会稍微快一些... - MartinStettner

0

有没有可能其他东西锁定了somefile.txt文件?

从本地(到文件)命令行进行简单检查。

net files

如果其他东西有锁定,这可能会给你一些线索。

或者你可以使用类似FileMon的工具获取更多细节,并检查你的应用程序是否正确释放。


0

既然这似乎不是编码问题,我要戴上我的系统管理员帽子,提供一些建议。

  1. 在客户端或服务器上运行病毒扫描程序,以便在创建文件时进行扫描。
  2. Windows opportunistic locking 有一个在网络共享上搞砸事情的习惯。我记得这主要是与具有平面文件数据库的多个读/写客户端有关,但缓存肯定可以解释你的问题。
  3. Windows file open cache。我不确定Win2K中是否仍存在此问题,但FileMon会告诉您。

编辑:如果您可以从服务器机器上捕获它的操作,那么Sysinternal的Handle将告诉您谁打开了它。


非常有趣。我以前在另一个应用程序中也遇到过问题,但我们发现罪魁祸首是AVG。我得研究一下这个。 - scottm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接