快速创建和删除文件会导致异常

问题描述 投票:1回答:3

我在针对.Net 4.5.2的单线程进程中运行了一些代码。此代码创建一个文件,执行一些操作然后删除该文件。大多数情况下,这种方法很好,但有时候会抛出异常。

我试图简化并重新创建下面的问题。从截图中可以看出,它设法创建和删除文件240次以上,然后使用UnauthorizedAccessException失败。有时下面的代码运行没有错误,有时它会越来越循环。那么这里发生了什么?

在生产环境中,文件被写入网络共享或从网络共享中删除,当使用网络共享时,抛出IOException如下(因此它在稍微不同的地方中断,在File.Delete期间而不是):

System.IO.IOException: The process cannot access the file '\\network_location\sub_dir\TMP1.sql' because it is being used by another process.
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.File.InternalDelete(String path, Boolean checkHost)

据我了解,using语句应调用dispose,导致文件流被刷新;所以在using语句之后我希望所有的数据都写到磁盘上。

我怎样才能确保按照我的预期一致地工作?

    static void Main(string[] args)
    {
        string networkFilePath = @"C:\Temp";
        string filename1 = "tmp1.sql";
        string filename2 = "tmp2.sql";

        string tempFilename1 = Path.Combine(networkFilePath, filename1);
        string tempFilename2 = Path.Combine(networkFilePath, filename2);

        int interations = 20000;

        Console.WriteLine("Started");
        try
        {
            for (int i = 0; i < interations; i++)
            {
                using (var s = new StreamWriter(tempFilename1))
                {
                    s.Write("aaa");
                }

                using (var s = new StreamWriter(tempFilename2))
                {
                    s.Write("aaa");
                }

                FileCompare(tempFilename1, tempFilename2);

                File.Delete(tempFilename1);
                File.Delete(tempFilename2);
            }
        }
        catch (IOException e)
        {
            Console.WriteLine(e.Message);
        }

        Console.WriteLine("Finished");
        Console.ReadLine();
    }

    public static bool FileCompare(string filename1, string filename2)
    {
        using (var fs1 = new FileStream(filename1, FileMode.Open))
        using (var fs2 = new FileStream(filename2, FileMode.Open))
        {
            return [... ommited for brevity, but just checks the content of file1 vs file2]
        }
    }

enter image description here

编辑我知道我可以使用临时文件名来避免这个问题...问题是,我怎么知道IO操作已经完成,例如,缓存数据是在本地还是在网络上写入磁盘?

编辑我修改了我的代码以关闭每个流,但仍然得到以下... enter image description here

c# .net delete-file
3个回答
1
投票

大多数情况下,当其他程序使用包含FileShare.Delete的许可共享模式打开相同文件时,会发生这种情况。这个共享允许另一个进程删除一个文件,但是在关闭它的所有句柄之前不会真正删除文件(包括用FileShare.Delete打开它的进程获得的句柄)。此类程序的示例可能是搜索索引器和防病毒软件 - 两者都可能想要扫描您创建的新文件,并且都不想锁定文件,因此将以非常宽松的共享模式打开它,包括FileShare.Delete

当您在已经使用File.Delete打开的文件上调用FileShare.Delete时 - 它将返回而没有任何错误,文件将被标记为删除(并将在所有句柄关闭后删除)。任何后续尝试访问此类“已删除”文件都将抛出您在示例中观察到的拒绝访问权限。

您可以使用以下代码轻松地重现它:

static void Main(string[] args) {
    var tmpFile = Path.GetTempFileName();
    // file is now created
    new Thread(() => {
        using (var fs = new FileStream(tmpFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) {
            // open with permissive share mode, including FileShare.Delete
            // and hold that handle for 5 seconds
            Thread.Sleep(5000);
        }
    }).Start();
    // wait a bit for thread above to grab a handle
    Thread.Sleep(1000);
    // delete, will return fine without any errors            
    File.Delete(tmpFile);
    // attempt to create - will fail with access denied exception you have
    using (var s = new StreamWriter(tmpFile)) {
        s.Write("aaa");
    }
    Console.WriteLine("done");
    Console.ReadLine();
}

因为您不知道另一个进程将持有文件句柄多长时间您将要删除 - 我认为您只能检查删除后文件是否仍然存在,如果是 - 请稍等一下再检查:

File.Delete(tmpFile);
while (File.Exists(tmpFile)) {
    // obviously don't loop forever
    // check for some time then throw exception
    Thread.Sleep(100);
}
// in the example above it will loop for 4 seconds while
// handle is hold by another thread, then finally return
// and subsequent creation of new file will work fine without exceptions

0
投票

正如Drag and Drop已评论过,添加.Close(),它应该被称为。只是为了确保流已关闭。

如果这不起作用,你可以使用随机或增加(1,2,3,...)文件名,因为它们无论如何都是临时的。对这些文件进行排队并让第二个线程尝试删除它们,直到成功为止。


0
投票

有时只有操作系统或GC保持文件句柄,两者都不受你的控制,并且不值得监视I / O,...你的目标似乎是可靠的清理,而不是I / O监控。

如果您想采用线程方法,可以将这些文件路径排队到spinwait线程以进行异常容错清理。

如果您不想添加线程,那么“异常时退出清理循环”可以放在处理周期的底部。

无论哪种方式,您都可以针对顽固拒绝删除的文件回答您自己的适当方法。考虑以下伪代码方法,将要清理的文件排队到跟踪删除重试的字典,以避免无限循环:

// ... declarative section, downgrade to Dictionary class if thread-safe concurrency is not needed
ConcurrentDictionary<string, int> files_for_cleanup = new ConcurrentDictionary<string, int>();

// ... bottom of your processing loop
if (!files_for_cleanup.ContainsKey(current_temp_file))
{
    files_for_cleanup.TryAdd(current_temp_file, 0);
}

// ... switching to cleanup thread, if threaded, otherwise continue with the bottom of your processing loop
KeyValuePair<string, int> item = null;
try
{
  while(!files_for_cleanup.IsEmpty)
  {
    item = files_for_cleanup.First();
    if(File.Exists(item.Key)
    {
      File.Delete(item.Key);
    }
    files_for_cleanup.TryRemove(item.Key, item.Value);
  }
}
catch(Exception)
{
  if (item != null)
  {
    if (item.Value > 40 /* retry threshold */)
    {
      files_for_cleanup.TryRemove(item.Key, item.Value);
      // ... perform other unrecoverable actions, e.g. logging, delete on reboot, ...
    }

    else
    {
      files_for_cleanup.TryUpdate(item.Key, item.Value, item.Value + 1);
    }
  }
}

//  ... if in thread, spinwait, sleep, etc.  otherwise return to top of loop
© www.soinside.com 2019 - 2024. All rights reserved.