有没有办法检查文件是否正在使用?

Daw*_*wsy 809 .net c# file-io file file-locking

我正在用C#编写一个需要重复访问1个图像文件的程序.大部分时间它都可以工作,但如果我的计算机运行速度很快,它会在将文件保存回文件系统之前尝试访问该文件并抛出错误:"另一个进程正在使用的文件".

我想找到解决这个问题的方法,但是我所有的谷歌搜索都只是通过使用异常处理来创建检查.这违背了我的宗教信仰,所以我想知道是否有人有更好的方法呢?

Spe*_*nce 541

您可能会遇到线程争用情况,其中有文档示例将此用作安全漏洞.如果您检查该文件是否可用,但是然后尝试使用它,那么您可能会抛出这一点,恶意用户可以使用它来强制和利用您的代码.

你最好的选择是尝试catch/finally,它试图获取文件句柄.

try
{
   using (Stream stream = new FileStream("MyFilename.txt", FileMode.Open))
   {
        // File/Stream manipulating code here
   }
} catch {
  //check here why it failed and ask user to retry if the file is in use.
}
Run Code Online (Sandbox Code Playgroud)

  • +1.没有100%安全的方法来"了解文件是否在使用中",因为在您进行检查后毫秒,该文件可能不再使用,反之亦然.相反,您只需打开文件并在没有例外的情况下使用它. (116认同)
  • using语句用于确保在完成后关闭流.我想你会发现using(){}比try {} finally {obj.Dispose()}更少.您还会发现现在需要在using语句之外声明对象引用,这更像是输入.如果你有一个明确的界面,你也必须施放.最后,您希望尽快处置,并且finally逻辑可能具有UI或任何其他长时间运行的操作,这些操作与调用IDispose几乎没有关系.</咆哮> (35认同)
  • 太糟糕的.NET不支持CAS.像TryOpenFile(Ref FileHandle)那样返回成功/失败的东西.总是应该有一种解决方法,而不仅仅依赖于异常处理.我想知道Microsoft Office是如何做到的. (8认同)
  • 这里要理解的关键是这个API只是使用windows API来获取文件句柄.因此,他们需要翻译从C API收到的错误代码并将其包装到异常中以进行抛出.我们在.Net中有异常处理,所以为什么不使用它.这样,您可以在代码中编写一个干净的前向路径,并将错误处理保留在单独的代码路径中. (2认同)
  • 这并没有否定你必须在try之外声明你的对象并且必须显式地调用dispose这一事实,这对你来说是使用for并且意味着同样的事情. (2认同)
  • @HarryJohnston` // File/Stream在这里操作代码` - 您应该在`try`块中使用该文件(读/写/等),从而避免了另一个进程可以锁定文件之间的竞争条件检查并打开 - 因为检查和打开是一个原子操作. (2认同)
  • @HarryJohnston不经意地写了黑盒子?不是重点.尝试使用Ms Excel打开仍在写入的xls文件.我们是否应该说因为这个原因而无法写出来?不,我们只是尽可能地等待文件不再使用,而不是给用户造成"文件锁定错误=>请再试一次".这里的两个解决方案都很有用,具体取决于实际情况.我的意见是为了反驳诸如关于ChrisW答案的"这是一个糟糕的答案"的陈述. (2认同)

Chr*_*isW 524

更新了此解决方案的注意事项:FileAccess.ReadWrite对于只读文件,检查将失败,因此已修改解决方案以进行检查FileAccess.Read.虽然此解决方案有效,因为FileAccess.Read如果文件上有写入或读取锁定,尝试检查将失败,但是,如果文件上没有写入或读取锁定,则此解决方案将无效,即它已打开(用于读取或写入)使用FileShare.Read或FileShare.Write访问.

原文: 过去几年我使用过这段代码,我没有遇到任何问题.

了解您对使用异常的犹豫,但您无法一直避免它们:

protected virtual bool IsFileLocked(FileInfo file)
{
    try
    {
        using(FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None))
        {
            stream.Close();
        }
    }
    catch (IOException)
    {
        //the file is unavailable because it is:
        //still being written to
        //or being processed by another thread
        //or does not exist (has already been processed)
        return true;
    }

    //file is not locked
    return false;
}
Run Code Online (Sandbox Code Playgroud)

  • -1.这是一个糟糕的答案,因为在IsFileLocked关闭之后,在您的线程有机会打开它之前,该文件可能会被另一个线程/进程锁定. (209认同)
  • 这是一个很好的解决方案,但我有一个注释 - 您可能不想以访问模式FileAccess.Read打开文件,因为如果文件恰好是只读的,ReadWrite将始终失败. (59认同)
  • @ChrisW:你可能想知道发生了什么.不要惊慌.你只是受到了每日WTF社区的愤怒:http://thedailywtf.com/Comments/The-Right-Way-to-Find-a-File.aspx#402913 (52认同)
  • 我认为这是一个很好的答案.我正在使用它作为[扩展方法](http://msdn.microsoft.com/en-us/library/bb383977.aspx)ála`publicstatic bool IsLocked(此FileInfo文件){/*...*/}`. (16认同)
  • @ChrisW为什么这是件坏事.这个社区在这里指出好的和坏的答案.如果一群专业人士注意到这是一件坏事,并加入到downvote,那么该网站就是WAI.在你得到否定之前,如果你读到那篇文章,他们会说"赞成正确的答案",而不是选择错误的答案.你是否希望他们在评论中解释他们的赞成票.谢谢你介绍我另一个好网站! (14认同)
  • 很好的答案,但问题是文件实际上可能不存在会导致FileNotFound异常 - 这是一个IOException.IOException catch块将捕获此信息并且方法结果将此解释为文件锁定,而实际上文件实际上并不存在 - 这可能导致难以诊断错误.从API的角度来看很棘手 - 也许API应该是返回枚举的GetFileState:Locked,NotFound,KnockYourselfOut.:)当然可能会引发UnauthorizedAccessException,这不是IOException ...... (7认同)
  • 使用这种方法的任何东西本身都可以适应竞争条件.例如,如果我调用此函数然后继续尝试写入文件它将失败.对文件的任何写入都应该直接处理,而不是事先调用"检查文件是否为X"逻辑.函数本身是"正确的",但是不谨慎/缺乏经验的使用会留下微妙的错误 (5认同)
  • 虽然Polyfun有一个观点,即当IsFileLocked返回true并且程序实际重新打开文件时,文件可能会被锁定.但是,只需返回已经打开的流句柄就可以很容易地避开它,如果它被锁定则返回null.在功能上与你可以在使用它之前简单地进行空检查一样,但它会在检查后保持文件打开. (5认同)
  • [恭喜](http://thedailywtf.com/articles/The-Right-Way-to-Find-a-File). (4认同)
  • 无论发生什么事情,马修总是被召唤.总是.点.:) (3认同)
  • 小注意:您可以考虑使用块来自动处理,或者至少自己处理流. (3认同)
  • -1这是一个直接错误的答案.这只检查此文件是否"正在使用",这意味着在您调用它之后,它可能会立即被另一个进程使用,您仍然会得到异常.@ BloodyRain2k,你是对的,但答案中没有指出这仍然是一个错误的答案. (3认同)
  • @PierreLebeaupin:那为什么不令人震惊? (2认同)
  • 不知道这篇文章是否仍在受到监控,但我会尝试一下:我们正在使用非常相似的代码(可能是您的),并且它对于小文件运行良好。但最近我们在处理非常大的文件(几GB)时遇到了一个问题:文件从服务器A复制到服务器B,我们在服务器B中获取它们并将它们复制到文件夹X,这是我们的进程输入文件夹。有时,我们的进程会将文件复制到文件夹 X,即使该文件尚未从服务器 A 完全复制到服务器 B - 我不知道为什么!任何帮助将不胜感激。 (2认同)

Jer*_*son 88

用它来检查文件是否被锁定:

using System.IO;
using System.Runtime.InteropServices;
internal static class Helper
{
const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;

private static bool IsFileLocked(Exception exception)
{
    int errorCode = Marshal.GetHRForException(exception) & ((1 << 16) - 1);
    return errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION;
}

internal static bool CanReadFile(string filePath)
{
    //Try-Catch so we dont crash the program and can check the exception
    try {
        //The "using" is important because FileStream implements IDisposable and
        //"using" will avoid a heap exhaustion situation when too many handles  
        //are left undisposed.
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) {
            if (fileStream != null) fileStream.Close();  //This line is me being overly cautious, fileStream will never be null unless an exception occurs... and I know the "using" does it but its helpful to be explicit - especially when we encounter errors - at least for me anyway!
        }
    }
    catch (IOException ex) {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex)) {
            // do something, eg File.Copy or present the user with a MsgBox - I do not recommend Killing the process that is locking the file
            return false;
        }
    }
    finally
    { }
    return true;
}
}
Run Code Online (Sandbox Code Playgroud)

出于性能原因,我建议您在同一操作中读取文件内容.这里有些例子:

public static byte[] ReadFileBytes(string filePath)
{
    byte[] buffer = null;
    try
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            int length = (int)fileStream.Length;  // get file length
            buffer = new byte[length];            // create buffer
            int count;                            // actual number of bytes read
            int sum = 0;                          // total number of bytes read

            // read until Read method returns 0 (end of the stream has been reached)
            while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
                sum += count;  // sum is a buffer offset for next reading

            fileStream.Close(); //This is not needed, just me being paranoid and explicitly releasing resources ASAP
        }
    }
    catch (IOException ex)
    {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex))
        {
            // do something? 
        }
    }
    catch (Exception ex)
    {
    }
    finally
    {
    }
    return buffer;
}

public static string ReadFileTextWithEncoding(string filePath)
{
    string fileContents = string.Empty;
    byte[] buffer;
    try
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            int length = (int)fileStream.Length;  // get file length
            buffer = new byte[length];            // create buffer
            int count;                            // actual number of bytes read
            int sum = 0;                          // total number of bytes read

            // read until Read method returns 0 (end of the stream has been reached)
            while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
            {
                sum += count;  // sum is a buffer offset for next reading
            }

            fileStream.Close(); //Again - this is not needed, just me being paranoid and explicitly releasing resources ASAP

            //Depending on the encoding you wish to use - I'll leave that up to you
            fileContents = System.Text.Encoding.Default.GetString(buffer);
        }
    }
    catch (IOException ex)
    {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex))
        {
            // do something? 
        }
    }
    catch (Exception ex)
    {
    }
    finally
    { }     
    return fileContents;
}

public static string ReadFileTextNoEncoding(string filePath)
{
    string fileContents = string.Empty;
    byte[] buffer;
    try
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            int length = (int)fileStream.Length;  // get file length
            buffer = new byte[length];            // create buffer
            int count;                            // actual number of bytes read
            int sum = 0;                          // total number of bytes read

            // read until Read method returns 0 (end of the stream has been reached)
            while ((count = fileStream.Read(buffer, sum, length - sum)) > 0) 
            {
                sum += count;  // sum is a buffer offset for next reading
            }

            fileStream.Close(); //Again - this is not needed, just me being paranoid and explicitly releasing resources ASAP

            char[] chars = new char[buffer.Length / sizeof(char) + 1];
            System.Buffer.BlockCopy(buffer, 0, chars, 0, buffer.Length);
            fileContents = new string(chars);
        }
    }
    catch (IOException ex)
    {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex))
        {
            // do something? 
        }
    }
    catch (Exception ex)
    {
    }
    finally
    {
    }

    return fileContents;
}
Run Code Online (Sandbox Code Playgroud)

亲自尝试一下:

byte[] output1 = Helper.ReadFileBytes(@"c:\temp\test.txt");
string output2 = Helper.ReadFileTextWithEncoding(@"c:\temp\test.txt");
string output3 = Helper.ReadFileTextNoEncoding(@"c:\temp\test.txt");
Run Code Online (Sandbox Code Playgroud)

  • 如果那里没有那么多"魔术数字",我会赞成http://en.wikipedia.org/wiki/Magic_number_(programming) (8认同)
  • 我指的是errorCode比较,而不是位移.虽然现在你提到它...... (3认同)
  • 遗憾的是,@ JeremyThompson将特定的`IOException`放在普通的之后.一般人将捕获所有路过的东西,特定的"IOException"将永远是孤独的.只需交换这两个. (3认同)

asd*_*101 15

我最近遇到了这个问题并发现了这个: https: //learn.microsoft.com/en-us/dotnet/standard/io/handling-io-errors

在这里,Microsoft 描述了以下方法来检查是否IOException由于锁定文件而导致:

catch (IOException e) when ((e.HResult & 0x0000FFFF) == 32 ) {
    Console.WriteLine("There is a sharing violation.");
}
Run Code Online (Sandbox Code Playgroud)


Kar*_*han 7

也许您可以使用FileSystemWatcher并监视Changed事件.

我自己没有用过,但它可能值得一试.如果filesystemwatcher在这种情况下有点重,我会选择try/catch/sleep循环.

  • 使用 FileSystemWatcher 没有帮助,因为 Created 和 Changed 事件在文件创建/更改开始时引发。与 .NET 应用程序通过 FileSystemEventHandler 回调运行所需的时间相比,即使是小文件也需要更多的时间来由操作系统写入和关闭。这太可悲了,但是除了估计访问文件之前的等待时间或遇到异常循环之外别无选择…… (3认同)
  • 确切地说,我遇到了这个问题,因为带有 FileSystemWatcher 的 Windows 服务尝试在进程关闭文件之前读取该文件。 (3认同)

ker*_*ode 6

只需按预期使用例外.接受文件正在使用中并重复尝试,直到您的操作完成.这也是最有效的,因为在行动之前不会浪费任何检查状态的周期.

例如,使用下面的功能

TimeoutFileAction(() => { System.IO.File.etc...; return null; } );
Run Code Online (Sandbox Code Playgroud)

可重复使用的方法,在2秒后超时

private T TimeoutFileAction<T>(Func<T> func)
{
    var started = DateTime.UtcNow;
    while ((DateTime.UtcNow - started).TotalMilliseconds < 2000)
    {
        try
        {
            return func();                    
        }
        catch (System.IO.IOException exception)
        {
            //ignore, or log somewhere if you want to
        }
    }
    return default(T);
}
Run Code Online (Sandbox Code Playgroud)


rbo*_*boy 6

上面接受的答案会遇到一个问题,如果文件已打开以使用 FileShare.Read 模式进行写入,或者文件具有只读属性,则代码将无法工作。这个修改后的解决方案最可靠,需要记住两件事(对于接受的解决方案也是如此):

  1. 它不适用于以写共享模式打开的文件
  2. 这没有考虑线程问题,因此您需要将其锁定或单独处理线程问题。

记住上述内容,这将检查文件是否被锁定以进行写入锁定以防止读取

public static bool FileLocked(string FileName)
{
    FileStream fs = null;

    try
    {
        // NOTE: This doesn't handle situations where file is opened for writing by another process but put into write shared mode, it will not throw an exception and won't show it as write locked
        fs = File.Open(FileName, FileMode.Open, FileAccess.ReadWrite, FileShare.None); // If we can't open file for reading and writing then it's locked by another process for writing
    }
    catch (UnauthorizedAccessException) // https://msdn.microsoft.com/en-us/library/y973b725(v=vs.110).aspx
    {
        // This is because the file is Read-Only and we tried to open in ReadWrite mode, now try to open in Read only mode
        try
        {
            fs = File.Open(FileName, FileMode.Open, FileAccess.Read, FileShare.None);
        }
        catch (Exception)
        {
            return true; // This file has been locked, we can't even open it to read
        }
    }
    catch (Exception)
    {
        return true; // This file has been locked
    }
    finally
    {
        if (fs != null)
            fs.Close();
    }
    return false;
}
Run Code Online (Sandbox Code Playgroud)

  • 确实如此,只能在任何给定的时刻进行检查(或订阅事件),这种方法相对于公认的解决方案的优势在于它可以检查只读属性和写锁,并且不会返回误报。 (2认同)

Jul*_*ian 5

static bool FileInUse(string path)
    {
        try
        {
            using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate))
            {
                fs.CanWrite
            }
            return false;
        }
        catch (IOException ex)
        {
            return true;
        }
    }

string filePath = "C:\\Documents And Settings\\yourfilename";
bool isFileInUse;

isFileInUse = FileInUse(filePath);

// Then you can do some checking
if (isFileInUse)
   Console.WriteLine("File is in use");
else
   Console.WriteLine("File is not in use");
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!

  • 您执行的实际检查没问题;将它放在函数中是一种误导。您不想在打开文件之前使用这样的函数。在函数内部,文件被打开、检查和关闭。然后程序员假设该文件仍然可以使用并尝试打开它以供使用。这很糟糕,因为它可以被另一个排队打开此文件的进程使用和锁定。在第一次打开(用于检查)和第二次打开(用于使用)之间,操作系统可能已经取消了您的进程并可能正在运行另一个进程。 (13认同)

小智 5

您可以返回一个任务,该任务将在流可用时立即为您提供。这是一个简化的解决方案,但这是一个很好的起点。这是线程安全的。

private async Task<Stream> GetStreamAsync()
{
    try
    {
        return new FileStream("sample.mp3", FileMode.Open, FileAccess.Write);
    }
    catch (IOException)
    {
        await Task.Delay(TimeSpan.FromSeconds(1));
        return await GetStreamAsync();
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以照常使用此流:

using (var stream = await FileStreamGetter.GetStreamAsync())
{
    Console.WriteLine(stream.Length);
}
Run Code Online (Sandbox Code Playgroud)

  • 从GetStreamAsync()中的递归直到堆栈溢出需要多少秒? (3认同)

cdi*_*ins 5

据我所知,这里有一些代码与接受的答案具有相同的功能,但代码较少:

    public static bool IsFileLocked(string file)
    {
        try
        {
            using (var stream = File.OpenRead(file))
                return false;
        }
        catch (IOException)
        {
            return true;
        }        
    }
Run Code Online (Sandbox Code Playgroud)

但我认为通过以下方式进行更稳健:

    public static void TryToDoWithFileStream(string file, Action<FileStream> action, 
        int count, int msecTimeOut)
    {
        FileStream stream = null;
        for (var i = 0; i < count; ++i)
        {
            try
            {
                stream = File.OpenRead(file);
                break;
            }
            catch (IOException)
            {
                Thread.Sleep(msecTimeOut);
            }
        }
        action(stream);
    }
Run Code Online (Sandbox Code Playgroud)


Ber*_*ard 5

除了工作 3-liners 和仅供参考:如果你想要完整的信息 - 微软开发中心有一个小项目:

https://code.msdn.microsoft.com/windowsapps/How-to-know-the-process-704839f4

现在发现:https : //github.com/TacticalHorse/LockFinder/blob/master/LockFinder.cs

从简介:

在 .NET Framework 4.0 中开发的 C# 示例代码将有助于找出锁定文件的进程。 rstrtmgr.dll中包含的RmStartSession函数已用于创建重启管理器会话,并根据返回结果创建 Win32Exception 对象的新实例。通过RmRegisterResources函数将资源注册到 Restart Manager 会话后 ,调用RmGetList函数通过枚举RM_PROCESS_INFO数组来检查哪些应用程序正在使用特定文件。

它通过连接到“重新启动管理器会话”来工作。

重新启动管理器使用在会话中注册的资源列表来确定必须关闭和重新启动哪些应用程序和服务。 资源可以通过文件名、服务短名称或描述正在运行的应用程序的RM_UNIQUE_PROCESS 结构来标识

对于您的特定需求,它可能有点过度设计……但如果这是想要的,请继续使用 vs-project。


归档时间:

查看次数:

494556 次

最近记录:

6 年,3 月 前