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

时间:2009-05-18 06:37:41

标签: c# .net file file-io file-locking

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

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

20 个答案:

答案 0 :(得分:531)

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

你最好的选择是试试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.
}

答案 1 :(得分:501)

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

<强> ORIGINAL: 我在过去的几年里使用过这段代码,而且我没有遇到任何问题。

了解您对使用例外的犹豫,但您无法一直避免这些:

protected virtual bool IsFileLocked(FileInfo file)
{
    FileStream stream = null;

    try
    {
        stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
    }
    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;
    }
    finally
    {
        if (stream != null)
            stream.Close();
    }

    //file is not locked
    return false;
}

答案 2 :(得分:83)

使用此选项检查文件是否已锁定:

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;
}
}

出于性能原因,我建议您在同一操作中阅读文件内容。以下是一些例子:

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;
}

自己尝试一下:

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

答案 3 :(得分:7)

也许你可以使用FileSystemWatcher并注意Changed事件。

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

答案 4 :(得分:6)

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

使用以下功能,例如

TimeoutFileAction(() => { System.IO.File.etc...; return null; } );

可重复使用的方法,在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);
}

答案 5 :(得分:4)

我最近遇到了这个问题并发现:https://docs.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.");
}

答案 6 :(得分:4)

我知道的唯一方法是使用Win32独占锁API,这种API不是太快,但存在示例。

对于一个简单的解决方案,大多数人只是尝试/捕获/睡眠循环。

答案 7 :(得分:4)

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

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();
    }
}

您可以照常使用此流:

using (var stream = await FileStreamGetter.GetStreamAsync())
{
    Console.WriteLine(stream.Length);
}

答案 8 :(得分:4)

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");

希望这有帮助!

答案 9 :(得分:3)

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

  1. 对于使用写共享模式打开的文件不起作用
  2. 这不会考虑线程问题,因此您需要将其锁定或单独处理线程问题。
  3. 请记住以上内容,这会检查文件是否已锁定以进行写入已锁定以阻止阅读

    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;
    }
    

答案 10 :(得分:2)

以下是一些代码,据我所知,它与接受的答案相同,但代码较少:

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

但是我认为以下列方式执行它会更加健壮:

    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);
    }

答案 11 :(得分:2)

您可以使用我的库访问多个应用程序中的文件。

您可以从nuget安装它:Install-Package Xabe.FileLock

如果您想了解更多相关信息,请查看 https://github.com/tomaszzmuda/Xabe.FileLock

ILock fileLock = new FileLock(file);
if(fileLock.Acquire(TimeSpan.FromSeconds(15), true))
{
    using(fileLock)
    {
        // file operations here
    }
}
仅当可以锁定此对象的独占文件时,

fileLock.Acquire方法才会返回true。 但是上传文件的app也必须在文件锁中做。 如果对象不可访问,则metod返回false。

答案 12 :(得分:2)

除了可以使用的3层衬纸,仅供参考:如果您想要全面介绍信息-Microsoft Dev Center上还有一个小项目:

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

摘自引言:

  

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

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

  

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

对于您的特殊需求,可能有些过度设计 ... 但是,如果这正是您想要的,请继续并获取vs项目。

答案 13 :(得分:2)

我曾经需要将PDF上传到在线备份档案中。但是,如果用户在另一个程序(例如PDF阅读器)中打开了文件,则备份将失败。匆忙中,我尝试了该线程中的一些最佳答案,但未能使它们起作用。对我来说有效的方法是尝试将PDF文件移动到其自己的目录。我发现如果在另一个程序中打开文件,这将失败,并且如果移动成功,则不需要还原操作,就像将其移动到单独的目录一样。我想发布我的基本解决方案,以防它对其他人的特定用例有用。

string str_path_and_name = str_path + '\\' + str_filename;
FileInfo fInfo = new FileInfo(str_path_and_name);
bool open_elsewhere = false;
try
{
    fInfo.MoveTo(str_path_and_name);
}
catch (Exception ex)
{
    open_elsewhere = true;
}

if (open_elsewhere)
{
    //handle case
}

答案 14 :(得分:1)

根据我的经验,您通常希望这样做,然后保护&#39;你的文件做了一些奇特的事情,然后使用&#39; protected&#39;文件。如果您只想使用这样一个文件,可以使用Jeremy Thompson在答案中解释的技巧。但是,如果您尝试在大量文件上执行此操作(例如,当您编写安装程序时),那么您将受到相当大的伤害。

这可以解决的一个非常优雅的方法是使用这样一个事实:如果使用其中一个文件,您的文件系统将不允许您更改文件夹名称。将文件夹保存在同一个文件系统中,它就像魅力一样。

请注意,您应该了解可以利用的明显方法。毕竟,文件不会被锁定。另请注意,还有其他原因可能导致Move操作失败。显然,正确的错误处理(MSDN)可以帮到这里。

var originalFolder = @"c:\myHugeCollectionOfFiles"; // your folder name here
var someFolder = Path.Combine(originalFolder, "..", Guid.NewGuid().ToString("N"));

try
{
    Directory.Move(originalFolder, someFolder);

    // Use files
}
catch // TODO: proper exception handling
{
    // Inform user, take action
}
finally
{
    Directory.Move(someFolder, originalFolder);
}

对于个别文件,我会坚持使用Jeremy Thompson发布的锁定建议。

答案 15 :(得分:0)

我很想知道这是否会触发任何WTF反应。我有一个过程,可以从控制台应用程序创建并随后启动PDF文档。但是,我正在处理一个脆弱的问题,如果用户多次运行该进程,生成相同的文件而不先关闭先前生成的文件,应用程序将抛出异​​常并死亡。这种情况经常发生,因为文件名基于销售报价编号。

我决定依靠自动递增的文件版本控制,而不是以这种不合理的方式失败:

private static string WriteFileToDisk(byte[] data, string fileName, int version = 0)
{
    try
    {
        var versionExtension = version > 0 ? $"_{version:000}" : string.Empty;
        var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{fileName}{versionExtension}.pdf");
        using (var writer = new FileStream(filePath, FileMode.Create))
        {
            writer.Write(data, 0, data.Length);
        }
        return filePath;
    }
    catch (IOException)
    {
        return WriteFileToDisk(data, fileName, ++version);
    }
}

可能会对catch块给予更多关注,以确保我捕获正确的IOException。我可能还会在启动时清除应用程序存储空间,因为这些文件无论如何都是临时的。

我意识到这超出了OP的问题范围,只是检查文件是否正在使用,但这确实是我到达这里时想要解决的问题,所以也许对其他人有用。

答案 16 :(得分:0)

这样的帮助吗?

var fileWasWrittenSuccessfully = false;
while (fileWasWrittenSuccessfully == false)
{
    try
    {
        lock (new Object())
        {
            using (StreamWriter streamWriter = new StreamWriter(filepath.txt"), true))
            {
                streamWriter.WriteLine("text");
            }
        }

        fileWasWrittenSuccessfully = true;
    }
    catch (Exception)
    {

    }
}

答案 17 :(得分:0)

retry_possibility:
//somecode here

try
{
    using(FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None))
    {
        stream.Close();
    }
    //write or open your file here
}
catch (IOException)
{
    DialogResult dialogResult = MessageBox.Show("This file is opened by you or another user. Please close it and press retry.\n"+ expFilePath, "File Locked", MessageBoxButtons.RetryCancel);
    if (dialogResult == DialogResult.Retry)
    {
        goto retry_possibility;
    }
    else if (dialogResult == DialogResult.Cancel)
    {
        //do nothing
    }
}

答案 18 :(得分:-2)

尝试将文件移动/复制到临时目录。如果可以,它没有锁定,您可以安全地在临时目录中工作而不会获得锁定。其他只是尝试在x秒内再次移动它。

答案 19 :(得分:-3)

我使用此解决方法,但是我在使用IsFileLocked函数检查文件锁定和打开文件之间有一段时间。在这个时间跨度中,其他一些线程可以打开文件,因此我将获得IOException。

所以,我为此添加了额外的代码。在我的情况下,我想加载XDocument:

        XDocument xDoc = null;

        while (xDoc == null)
        {
            while (IsFileBeingUsed(_interactionXMLPath))
            {
                Logger.WriteMessage(Logger.LogPrioritet.Warning, "Deserialize can not open XML file. is being used by another process. wait...");
                Thread.Sleep(100);
            }
            try
            {
                xDoc = XDocument.Load(_interactionXMLPath);
            }
            catch
            {
                Logger.WriteMessage(Logger.LogPrioritet.Error, "Load working!!!!!");
            }
        }
你怎么看?我可以换一些东西吗?也许我根本不必使用IsFileBeingUsed函数?

由于