How to tell if the file is being used by another process before attempting to read

7

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

  

System.IO.IOException: Process can not access file   'C: \ XXXXXX \ xxxx.xxx' because it is being used by another process.

The code that throws the exception is:

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

Is there any way for me to know if it's being used before running ReadAllBytes ? Is there any other solution? Read it another way?

I know this happens because some other routine of mine should be giving lock to the file, but this is eventual, one in 5000 times. So I'm having a hard time finding a solution.

    
asked by anonymous 23.05.2015 / 03:29

2 answers

8

In a simple way, you can work with the file in a block Try/Catch , in the occurrence of exceptions, for example IOException , you check the error code returned, if it is ERROR_SHARING_VIOLATION or ERROR_LOCK_VIOLATION , the file is in use.

As this answer , you can implement this 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 Restart Manager API, according to How do I find out which process has a file open? , in English , the purpose of this API is:

  

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

     

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

This answer SOEN suggests implementing 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;
    }
}

You need to include the namespaces :

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

Example usage:

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

Image:

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

Article about the subject: Restart Manager and Generic Method Compilation - MSDN Magazine SubscribeReader ServicesCommunityPartner Resources

    
23.05.2015 / 05:11
3

Good I / O practices dictate that you should not check if the file is in use before you open it, because that check 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 call to 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 it and the time when we try to open it.

Simply open the file and catch the exception if one is thrown.

    
23.05.2015 / 11:20