How to know if the file is being used by another process before trying to read

Asked

Viewed 6,238 times

7

My system does N jobs on top of a list of files, eventually I get this exception:

System.IO.Ioexception: Process cannot access file 'C: XXXXXX xxxx.xxx' because it is being used by another process.

The code that sets the exception is:

if (File.ReadAllBytes(file).Length > 0 )
{
   ...
}

Is there any way I know if it’s being used before I run the ReadAllBytes? Is there any other solution? Read otherwise?

I know this happens because some other routine of mine must be giving lock in the archive, but this is occasional, an 5000 times. So I’m having trouble finding a solution.

  • "I know this happens because some other routine of mine must be locking the file" - so there are several threads accessing the same file? Then the solution is to find the race condition and properly synchronize the threads.

2 answers

8


Simply put, you can work with the file in a block Try/Catch, in the event of exceptions, for example IOException, Voce checks the error code returned, if it is ERROR_SHARING_VIOLATION or ERROR_LOCK_VIOLATION, the file is in use.

In accordance with this answer, you can implement it as follows:

public bool ArquivoEmUso (string arquivo) {
    try {
        using (File.Open(arquivo, FileMode.Open)) {}
    }
    catch (IOException e) {
        var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1);
        return errorCode == 32 || errorCode == 33;
    }

    return false;
}

Another, more appropriate way is to use the API Restart Manager, According to the page How do I find out which process has an open file?, in English, the purpose of this API is:

The official objective of Restart Manager is to help make it possible shut down and restart the applications that are using a file that you wish to update.

In order to do this, it needs to keep track of which processes are holding references to which files. [...]

This answer of SOEN suggests to implement this as follows:

static public class FileUtil {
    [StructLayout(LayoutKind.Sequential)]
    struct RM_UNIQUE_PROCESS {
        public int dwProcessId;
        public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
    }

    const int RmRebootReasonNone = 0;
    const int CCH_RM_MAX_APP_NAME = 255;
    const int CCH_RM_MAX_SVC_NAME = 63;

    enum RM_APP_TYPE {
        RmUnknownApp = 0, RmMainWindow = 1, RmOtherWindow = 2, RmService = 3,
        RmExplorer = 4, RmConsole = 5, RmCritical = 1000
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct RM_PROCESS_INFO {
        public RM_UNIQUE_PROCESS Process;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
        public string strAppName;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
        public string strServiceShortName;

        public RM_APP_TYPE ApplicationType;
        public uint AppStatus;
        public uint TSSessionId;
        [MarshalAs(UnmanagedType.Bool)]
        public bool bRestartable;
    }

    [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
    static extern int RmRegisterResources(uint pSessionHandle,
                                          UInt32 nFiles,
                                          string[] rgsFilenames,
                                          UInt32 nApplications,
                                          [In] RM_UNIQUE_PROCESS[] rgApplications,
                                          UInt32 nServices,
                                          string[] rgsServiceNames);

    [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
    static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, 
                                     string strSessionKey);

    [DllImport("rstrtmgr.dll")]
    static extern int RmEndSession(uint pSessionHandle);

    [DllImport("rstrtmgr.dll")]
    static extern int RmGetList(uint dwSessionHandle,
                                out uint pnProcInfoNeeded,
                                ref uint pnProcInfo,
                                [In, Out] RM_PROCESS_INFO[] rgAffectedApps,
                                ref uint lpdwRebootReasons);

    /// <summary>
    /// Encontra quais processo(s) têm um bloqueio no arquivo especificado
    /// </summary>
    /// <param name="path">Caminho do arquivo</param>
    /// <returns>Processo(s) que bloqueiam o arquivo</returns>
    /// <remarks>Veja também:
    /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
    /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs
    /// </remarks>
    static public List<Process> WhoIsLocking(string path) {
        uint handle;
        string key = Guid.NewGuid().ToString();
        List<Process> processes = new List<Process>();

        int res = RmStartSession(out handle, 0, key);
        if (res != 0) 
          throw new Exception("Não foi possível iniciar a sessão de reinício. Não foi possível determinar arquivo bloqueador.");

        try {
            const int ERROR_MORE_DATA = 234;
            uint pnProcInfoNeeded = 0,
                 pnProcInfo = 0,
                 lpdwRebootReasons = RmRebootReasonNone;

            // Apenas verificando em um recurso
            string[] resources = new string[] { path };

            res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);

            if (res != 0) throw new Exception("Não foi possível registrar o recurso.");

            //Nota: Há uma condição de corrida aqui - a primeira chamada de RmGetList() 
            //retorna o número total de processos. No entanto, quando chamamos RmGetList()
            //novamente para obter os processos reais, este número pode ter aumentado.
            res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);

            if (res == ERROR_MORE_DATA) {
                // Criar um array para armazenar os resultados
                RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
                pnProcInfo = pnProcInfoNeeded;

                // Obtém a lista 
                res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
                if (res == 0) {
                    processes = new List<Process>((int)pnProcInfo);

                    //Enumerar todos os resultados e adiciona na lista a ser devolvida
                    for (int i = 0; i < pnProcInfo; i++) {
                        try {
                            processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
                        }
                        //Capturar o erro - no caso de o processo não está sendo executado
                        catch (ArgumentException) { }
                    }
                }
                else throw new Exception("Não foi possível listar os processos do recurso bloqueador.");
            }
            else if (res != 0) throw new Exception("Não foi possível listar os processos de recurso bloqueador. Falha ao obter o tamanho do resultado.");
        }
        finally {
            RmEndSession(handle);
        }

        return processes;
    }
}

It is necessary to include namespaces:

using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;

Example of use:

static void Main(string[] args) {
    string arquivo = @"C:\Path\Do\Arquivo";
    List<Process> processos = FileUtil.WhoIsLocking(arquivo);

    foreach (Process processo in processos) {
        Console.WriteLine(string.Format("O arquivo {0} está em uso!", Path.GetFileName(arquivo)));
        Console.WriteLine(string.Format("Nome do Processo {0}, PID: {1}", processo.ProcessName, processo.Id));
    }
    Console.ReadLine();
}

Imagery:

inserir a descrição da imagem aqui

Note: To API Restart Manager is available from Windows Server 2008 and Windows Vista.

Article on the subject: Restart Manager and Generic Method Compilation - MSDN Magazine

3

Good I/O practice dictates that you should not check whether the file is in use before opening it, because such checking would be useless.

For example, given the following pseudo-code:

if(File.IsNotBeingUsed(filename))
    var file = File.Open(filename);

Nothing prevents another process from simultaneously opening the file between the IsNotBeingUsed and the call to File.Open.

The same logic applies to checking if a file exists before opening it - nothing prevents another process from deleting the file in parallel between the time we check and the time we try to open it.

Simply open the file and get the exception if any is thrown.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.