关于C#:是否有方法检查文件是否在使用中?

Is there a way to check if a file is in use?

我正在用C语言编写一个程序,需要反复访问1个图像文件。大部分时间它都能工作,但是如果我的计算机运行得很快,它会在文件保存回文件系统之前尝试访问该文件,并抛出一个错误:"文件正被另一个进程使用"。

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


您可能会遇到线程争用情况,在此情况下,有文档记录的将此作为安全漏洞的示例。如果您检查该文件是否可用,然后尝试使用它,您可能会在此时抛出,恶意用户可以使用它来强制和利用您的代码。

您的最佳选择是尝试捕获/最终获取文件句柄。

1
2
3
4
5
6
7
8
9
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.
}


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

原件:在过去的几年里,我一直使用这个代码,并且没有遇到任何问题。

理解您对使用异常的犹豫,但您不能总是避免它们:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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;
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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;
}

自己试试:

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


也许您可以使用文件系统监视程序来监视更改的事件。

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


按预期使用异常。接受该文件正在使用,然后重复尝试,直到操作完成。这也是最有效的,因为您不会浪费任何周期,在执行之前检查状态。

例如,使用下面的函数

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

2秒后超时的可重用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}

您可以返回一个任务,一旦它可用,它就会给您一个流。这是一个简化的解决方案,但它是一个很好的起点。它是线程安全的。

1
2
3
4
5
6
7
8
9
10
11
12
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();
    }
}

您可以像往常一样使用此流:

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


我所知道的唯一方法是使用win32独占锁API,它速度不太快,但存在一些示例。

大多数人,为了简单地解决这个问题,只需尝试/catch/sleep循环。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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");

希望这有帮助!


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

  • 对于以写共享模式打开的文件,它将不起作用。
  • 这不考虑线程问题,因此需要将其锁定或单独处理线程问题。
  • 记住上述内容,这将检查文件是否被锁定以进行写入或锁定以防止读取:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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;
    }


    以下是一些代码,据我所知,它们与公认的答案做的是相同的事情,但代码较少:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        public static bool IsFileLocked(string file)
        {
            try
            {
                using (var stream = File.OpenRead(file))
                    return false;
            }
            catch (IOException)
            {
                return true;
            }        
        }

    不过,我认为以以下方式进行这项工作更为稳健:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
        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);
        }

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

    您可以从nuget:install包xabe.filelock安装它

    如果您想了解更多信息,请查看https://github.com/tomaszzmuda/xabe.filelock

    1
    2
    3
    4
    5
    6
    7
    8
    ILock fileLock = new FileLock(file);
    if(fileLock.Acquire(TimeSpan.FromSeconds(15), true))
    {
        using(fileLock)
        {
            // file operations here
        }
    }

    只有在此对象可以以独占方式锁定文件时,FileLock.Acquire方法才会返回true。但上传文件的应用程序也必须在文件锁定中完成。如果对象不可访问,则metod返回false。


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

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

    引言:

    The C# sample code developed in .NET Framework 4.0 would help in
    finding out which is the process that is having a lock on a file.
    RmStartSession function which is included in rstrtmgr.dll has been
    used to create a restart manager session and according to the return
    result a new instance of Win32Exception object is created. After
    registering the resources to a Restart Manager session via
    RmRegisterRescources function, RmGetList function is invoked to check
    what are the applications are using a particular file by enumerating
    the RM_PROCESS_INFO array.

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

    The Restart Manager uses the list of resources registered with the session to
    determine which applications and services must be shut down and restarted.
    Resources can be identified by filenames, service short names, or
    RM_UNIQUE_PROCESS structures that describe running applications.

    它可能会有点过度设计,以满足您的特殊需求…但如果这是你想要的,那就去拿vs项目吧。


    以我的经验,你通常想这样做,然后"保护"你的文件做一些花哨的事情,然后使用"受保护"的文件。如果你只有一个这样的文件,你可以使用杰里米·汤普森在答案中解释的技巧。但是,如果您尝试在许多文件上执行此操作(例如,当您编写安装程序时),则会受到相当大的伤害。

    解决这一问题的一个非常优雅的方法是使用这样一个事实:如果正在使用其中一个文件,您的文件系统将不允许您更改文件夹名。把文件夹放在同一个文件系统中,它会很有魅力。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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);
    }

    对于个别文件,我会坚持杰里米·汤普森的锁定建议。


    有什么需要帮忙的吗?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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)
        {

        }
    }

    我有兴趣看看这是否会触发WTF反射。我有一个从控制台应用程序创建并随后启动PDF文档的过程。但是,我正在处理一个弱点,如果用户多次运行进程,在没有先关闭之前生成的文件的情况下生成相同的文件,那么应用程序将抛出一个异常并死掉。这是非常常见的情况,因为文件名基于销售报价编号。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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的问题范围,即简单地检查文件是否在使用中,但这确实是我到达这里时要解决的问题,因此它可能对其他人有用。


    尝试将文件移动/复制到临时目录。如果可以的话,它没有锁,您可以在temp目录中安全工作而不需要锁。否则,只需在X秒内再次移动。


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

    所以,我为这个添加了额外的代码。在我的例子中,我希望加载xdocument:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
            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!!!!!");
                }
            }

    你怎么认为?我能换点东西吗?也许我根本不需要使用isfilebeinggured函数?

    谢谢