System.IO.IOException:“文件存在”时使用System.IO.Path.GetTempFileName() - 解决方案?

问题描述 投票:70回答:6

我的一个客户在尝试使用我的产品时遇到了异常。我获得了发生的异常的callstack,其顶部是:

at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.__Error.WinIOError()
   at System.IO.Path.GetTempFileName()
   at System.Windows.Input.Cursor.LoadFromStream(Stream cursorStream)
   at System.Windows.Input.Cursor..ctor(Stream cursorStream)

谷歌搜索这个,我发现有很多blog posts说明当%TEMP%文件夹中有超过65535个临时文件时抛出此异常,并且解决方案是简单地清除旧的临时文件。我可以要求客户这样做,但这可能只是一个临时的解决方案 - 如果他们经常运行其他一些软件,经常调用GetTempFileName,这会使问题一遍又一遍地重复出现?

我不能只是以编程方式清除%TEMP%文件夹,因为这可能以某种方式损坏其他东西,我不能避免调用GetTempFileName(并使用我自己的临时文件夹),因为它不是我,而是WPF代码调用它。

这有什么永久的解决方案吗?

更新:我已经确认%TEMP%文件夹溢出日志文件的问题不是由我自己的代码引起的,而且必须由客户机器上的其他第三方应用程序引起。我也查看了Cursor.LoadFromStream的实现,它肯定没有错 - 它生成一个临时文件,但随后在finally块中删除它。

c# .net
6个回答
15
投票

正如我在上一篇评论中提到的,我认为你唯一安全的方法是询问用户是否要删除文件并重试。你必须让用户输入这个,这样他们自担风险。在我脑海里它的东西类似于。

public Stream GetStream(Stream cursorStream)
{
    try
    {
       //getting stream
    }
    catch(IOE)
    {
        MessageBox.Show(this, "Unable to get stream, your temporary
                              folder may be full, do you want to try deleting 
                                some and try again?");
         if(yes)
         try
         {
             //delete and try again
             return GetStream(cursorStream);
         }
         catch(IOE)
          {
                //no luck
           }
          else
              return null;
    }

}

可选的检查,以确保可以,

Directory.EnumerateFiles(Path.GetTempPath(), "*", SearchOption.TopLevelOnly)
  .Count() == ushort.MaxValue;

17
投票

如果您在生产环境中或使用无法更改的应用程序发生这种情况,则快速解决方法是清空Temp文件夹。

根据运行应用程序的用户,您应该

  • 清空C:\Windows\Temp(用于IIS或在LocalSystem帐户下运行的服务)
  • %temp%本地登录用户(对我来说是C:\Users\MyUserName\AppData\Local\Temp)。

另一方面,如果你自己的代码抛出这个,你想要再次发生这种情况:

  1. 不要使用System.IO.Path.GetTempFileName()!

GetTempFileName()two decades old Win32 Api的包装。它会生成很容易发生碰撞的文件名。它通过在文件系统上大量循环,将可能的文件名从"%temp%\tmp0000.tmp"迭代到"tmpFFFF.tmp"并跳过已存在的文件系统来绕过这些碰撞。这是一个I / O密集,缓慢,坦率的可怕算法。同样仅使用4个十六进制字符是在失败之前对65536文件进行人为限制的原因。

另一种方法是生成不会发生冲突的文件名。例如,让我们重用GUID's逻辑:32个十六进制数字几乎不会碰撞。

private string GetTempFileName()
{
    return Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
}
// Sample: c:\Windows\Temp\2e38fe87-f6bb-4b0d-90b3-2d07016324c1

这将极限从65k扩展到4k百万个文件(理论上)......当然,泄漏65k文件已经很糟糕了,所以...

  1. 不要泄漏临时文件!

仔细检查您的应用程序是否有所有快乐和不快乐的路径(如意外的异常)。确保它正确处理每个FileStream并删除Finally块中的临时文件。

  1. 清理临时文件夹

立即清理它,并教育系统管理员定期清理它,因为你无法信任野外的每个应用程序。在我自己的服务器上,我将使用以下方法自动执

  • 对于全球Windows \ Temp

schtasks /Create /TR "cmd /c call DEL /F /S /Q %^TEMP%" /TN "Delete Global Temp Files" /sc WEEKLY /ST 12:00 /ru system

  • 对于当前用户:

schtasks /Create /TR "cmd /c call DEL /F /S /Q %^TEMP%" /TN "Delete %username% Temp Files" /sc WEEKLY /ST 12:00


5
投票

这是我最后使用的代码,并且在我的应用程序的初始化代码路径中提前放置,之后可能会发生对Cursor.LoadFromStream的任何调用:

    private void WarnUserIfTempFolderFull()
    {
        string tempFile = null;
        try
        {
            tempFile = Path.GetTempFileName();
        }
        catch (IOException e)
        {
            string problem = "The Temporary Folder is full.";

            string message = "{ProductName} has detected that the Windows Temporary Folder is full. \n" + 
                             "This may prevent the {ProductName} from functioning correctly.\n" + 
                             "Please delete old files in your temporary folder (%TEMP%) and try again.";

            Logger.Warn(problem);

            MessageBox.Show(message, caption: problem);
        }
        finally
        {
            if (tempFile != null) File.Delete(tempFile);
        }
    }

2
投票

解决方案:

  1. 正确的那一个。检测哪个应用程序生成了这么多临时文件而不删除它们。像Process monitor这样的公用事业应该会帮助你。然后修复应用程序或将其丢弃。是的,这可能是您的应用程序。这就是为什么我建议你去发现邪恶的来源。
  2. 最简单的一个。使用您自己的临时目录。如果从您的代码创建文件,这将无济于事。
  3. 最丑陋的一个。从应用程序中清除临时目录。你对结果的看法是完全正确的 - 你可以打破另一个应用程序。

1
投票

正如Sayse建议的那样,您可以尝试在应用启动时设置%TEMP%环境变量。

Environment.SetEnvironmentVariable("TEMP", "<dir>");

0
投票

对于遇到此问题并且找不到任何溢出的临时文件夹的任何其他人 - 请检查“C:/ Windows / Temp”文件夹。清理此文件夹解决了我的问题。

© www.soinside.com 2019 - 2024. All rights reserved.