IOException: el proceso no puede acceder al archivo 'ruta de archivo' porque está siendo utilizado por otro proceso

172

Tengo un código y cuando se ejecuta, arroja un IOException, diciendo que

El proceso no puede acceder al archivo 'nombre de archivo' porque otro proceso lo está utilizando

¿Qué significa esto y qué puedo hacer al respecto?

Adriano Repetti
fuente
1
Consulte esta pregunta para saber cómo encontrar el otro proceso que está utilizando el archivo.
stomy

Respuestas:

274

¿Cual es la causa?

El mensaje de error es bastante claro: está intentando acceder a un archivo y no es accesible porque otro proceso (o incluso el mismo proceso) está haciendo algo con él (y no permitió compartirlo).

Depuración

Puede ser bastante fácil de resolver (o bastante difícil de entender), dependiendo de su escenario específico. A ver algunos.

Su proceso es el único que accede a ese archivo.
Está seguro de que el otro proceso es suyo. Si sabe que abre ese archivo en otra parte de su programa, antes que nada debe verificar que cierre correctamente el identificador de archivo después de cada uso. Aquí hay un ejemplo de código con este error:

var stream = new FileStream(path, FileAccess.Read);
var reader = new StreamReader(stream);
// Read data from this file, when I'm done I don't need it any more
File.Delete(path); // IOException: file is in use

Afortunadamente se FileStreamimplementa IDisposable, por lo que es fácil envolver todo su código dentro de una usingdeclaración:

using (var stream = File.Open("myfile.txt", FileMode.Open)) {
    // Use stream
}

// Here stream is not accessible and it has been closed (also if
// an exception is thrown and stack unrolled

Este patrón también asegurará que el archivo no se dejará abierto en caso de excepciones (puede ser la razón por la que el archivo está en uso: algo salió mal y nadie lo cerró; consulte esta publicación para ver un ejemplo).

Si todo parece estar bien (está seguro de que siempre cierra todos los archivos que abre, incluso en caso de excepciones) y tiene múltiples hilos de trabajo, entonces tiene dos opciones: reelaborar su código para serializar el acceso al archivo (no siempre factible y no siempre deseado) o aplicar un patrón de reintento . Es un patrón bastante común para las operaciones de E / S: intenta hacer algo y, en caso de error, espera e intenta nuevamente (¿se preguntó por qué, por ejemplo, Windows Shell tarda un tiempo en informarle que un archivo está en uso? y no se puede eliminar?). En C # es bastante fácil de implementar (vea también mejores ejemplos sobre E / S de disco , redes y acceso a bases de datos ).

private const int NumberOfRetries = 3;
private const int DelayOnRetry = 1000;

for (int i=1; i <= NumberOfRetries; ++i) {
    try {
        // Do stuff with file
        break; // When done we can break loop
    }
    catch (IOException e) when (i <= NumberOfRetries) {
        // You may check error code to filter some exceptions, not every error
        // can be recovered.
        Thread.Sleep(DelayOnRetry);
    }
}

Tenga en cuenta un error común que vemos muy a menudo en StackOverflow:

var stream = File.Open(path, FileOpen.Read);
var content = File.ReadAllText(path);

En este caso ReadAllText()fallará porque el archivo está en uso ( File.Open()en la línea anterior). Abrir el archivo de antemano no solo es innecesario sino también incorrecto. Lo mismo se aplica a todas las Filefunciones que no devuelven un mango para el archivo que está trabajando: File.ReadAllText(), File.WriteAllText(), File.ReadAllLines(), File.WriteAllLines()y otros (como File.AppendAllXyz()funciones) hará todo lo abren y cierran el archivo por sí mismos.

Su proceso no es el único que accede a ese archivo
Si su proceso no es el único que accede a ese archivo, la interacción puede ser más difícil. Un patrón de reintento ayudará (si el archivo no debe ser abierto por otra persona pero lo es, entonces necesita una utilidad como Process Explorer para verificar quién está haciendo qué ).

Formas de evitar

En su caso, utilizar siempre el uso de declaraciones a los archivos abiertos. Como se dijo en el párrafo anterior, lo ayudará activamente a evitar muchos errores comunes (consulte esta publicación para ver un ejemplo sobre cómo no usarlo ).

Si es posible, intente decidir quién posee el acceso a un archivo específico y centralice el acceso a través de algunos métodos conocidos. Si, por ejemplo, tiene un archivo de datos donde su programa lee y escribe, entonces debe colocar todo el código de E / S dentro de una sola clase. Facilitará la depuración (porque siempre puede poner un punto de interrupción allí y ver quién está haciendo qué) y también será un punto de sincronización (si es necesario) para acceso múltiple.

No olvide que las operaciones de E / S siempre pueden fallar, un ejemplo común es este:

if (File.Exists(path))
    File.Delete(path);

Si alguien elimina el archivo después, File.Exists()pero antes File.Delete(), lo arrojará IOExceptiona un lugar en el que puede sentirse incorrectamente seguro.

Siempre que sea posible, aplique un patrón de reintento , y si está utilizando FileSystemWatcher, considere posponer la acción (porque recibirá una notificación, pero una aplicación aún puede estar funcionando exclusivamente con ese archivo).

Escenarios avanzados
No siempre es tan fácil, por lo que es posible que deba compartir el acceso con otra persona. Si, por ejemplo, está leyendo desde el principio y escribiendo hasta el final, tiene al menos dos opciones.

1) compartir lo mismo FileStreamcon las funciones de sincronización adecuadas (porque no es seguro para subprocesos ). Vea esto y esto publicaciones para un ejemplo.

2) utilice la FileShareenumeración para indicar al sistema operativo que permita que otros procesos (u otras partes de su propio proceso) accedan al mismo archivo simultáneamente.

using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.Read))
{
}

En este ejemplo, mostré cómo abrir un archivo para escribir y compartir para leer; tenga en cuenta que al leer y escribir superposiciones, se obtienen datos no definidos o no válidos. Es una situación que debe manejarse al leer. También tenga en cuenta que esto no hace que el acceso sea streamseguro para subprocesos, por lo que este objeto no se puede compartir con varios subprocesos a menos que el acceso esté sincronizado de alguna manera (ver enlaces anteriores). Hay otras opciones para compartir disponibles y abren escenarios más complejos. Consulte MSDN para más detalles.

En general, N procesos pueden leer del mismo archivo todos juntos, pero solo uno debe escribir, en un escenario controlado, incluso puede habilitar escrituras concurrentes, pero esto no puede generalizarse en pocos párrafos de texto dentro de esta respuesta.

¿Es posible desbloquear un archivo utilizado por otro proceso? No siempre es seguro y no es tan fácil, pero sí, es posible .

Adriano Repetti
fuente
No sé qué hay de malo en mi código, lo uso usando bloques, pero todavía hay un error que dice que el archivo está en uso cuando intento eliminarlo.
Jamshaid Kamran
No, en realidad tengo un control de temporizador haciendo eso, además del temporizador, si llamo a la función nuevamente, arrojará una excepción. Básicamente estoy descifrando un archivo en otro archivo de texto, después de eso borro el archivo recién creado, ¡eso arroja la excepción en la eliminación!
Jamshaid Kamran
3
@ جمشیدکامران ¿Por qué crear un archivo con el contenido que tiene en su código y luego eliminarlo? Parece extraño crear el archivo en primer lugar, entonces. Como no publicó el código, no sabemos lo que está haciendo. Pero cuando crea su archivo, si lo hace File.Create(path), debe agregar .Close()al final de eso antes de escribir en él. Hay escollos como ese, además de las usingdeclaraciones para escribir los archivos y luego eliminarlos. Debe publicar el código en su pregunta sobre cómo está creando y eliminando su archivo. Pero probablemente se alinea con algo mencionado anteriormente.
vapcguy
Mi código usa Directory.SetCreationTimeUTC()pero falla cuando File Explorer está abierto, alegando que otro proceso está accediendo al directorio. ¿Cómo debo manejar esta situación?
Kyle Delaney
1
@KyleDelaney Diría que debe esperar hasta que la carpeta se haya cerrado, si no es cuestión de unos segundos, todo se volverá complejo rápidamente (¿mantener una cola en segundo plano con operaciones pendientes? ¿Observador del sistema de archivos? ¿Sondeo?) publicar una pregunta con más detalles para una respuesta ad-hoc
Adriano Repetti
29

El uso de FileShare solucionó mi problema de abrir el archivo incluso si lo abre otro proceso.

using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
{
}
Muhammad Umar
fuente
9

Tuve un problema al subir una imagen y no pude eliminarlo y encontré una solución. gl hf

//C# .NET
var image = Image.FromFile(filePath);

image.Dispose(); // this removes all resources

//later...

File.Delete(filePath); //now works
Hudson
fuente
2
Si está eliminando un archivo, primero debe deshacerse del objeto de imagen.
shazia
1
Bien, busqué esta solución. También tuve un problema con la función Image.FromFile.
el vástago del
1
@thescion :) np
Hudson
1
Esto funcionó de manera brillante para mí y resolvió mi problema exacto. Quería procesar una carpeta de archivos Tiff, convertirlos a secuencias de bytes [] y enviarlos a un servicio, y luego mover la carpeta de archivos Tiff a una carpeta de archivo "Procesado". Image.FromFile coloca un bloqueo en el archivo Tiff y no lo libera a tiempo, por lo que cuando iba a mover los archivos Tiff y la carpeta que contenía, recibía el error "siendo utilizado por otro proceso", ya que los bloqueos todavía estaban en su lugar. Hacer el .Release justo después de obtener los bytes del archivo Tiff solucionó totalmente este problema de archivo bloqueado.
Desarrollador63
4

Recibí este error porque estaba haciendo File.Move a una ruta de archivo sin un nombre de archivo, necesito especificar la ruta completa en el destino.

vive el amor
fuente
Y no solo sin un nombre de archivo tampoco. Un nombre de archivo ilegal (destino), en mi caso "... \ file". - ¡Dará este mismo estúpido error y te indicará en la dirección equivocada durante medio día!
Nick Westgate
3

El error indica que otro proceso está intentando acceder al archivo. Tal vez usted o alguien más lo tiene abierto mientras intenta escribirle. "Leer" o "Copiar" generalmente no causa esto, pero escribir o llamar a eliminar en él sí lo haría.

Hay algunas cosas básicas para evitar esto, como han mencionado otras respuestas:

  1. En FileStreamoperaciones, colóquelo en un usingbloque con un FileShare.ReadWritemodo de acceso.

    Por ejemplo:

    using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
    {
    }
    

    Tenga en cuenta que FileAccess.ReadWriteno es posible si lo usa FileMode.Append.

  2. Me encontré con este problema cuando estaba usando una secuencia de entrada para hacer File.SaveAscuando el archivo estaba en uso. En mi caso, descubrí que en realidad no necesitaba volver a guardarlo en el sistema de archivos, así que terminé eliminando eso, pero probablemente podría haber intentado crear un FileStream en una usingdeclaración FileAccess.ReadWrite, al igual que el código encima.

  3. Guardar sus datos como un archivo diferente y volver a eliminar el antiguo cuando se descubra que ya no está en uso, luego cambiar el nombre del que se guardó correctamente al nombre del original es una opción. La forma en que se prueba el uso del archivo se realiza a través de

    List<Process> lstProcs = ProcessHandler.WhoIsLocking(file);

    en mi código a continuación, y podría hacerse en un servicio de Windows, en un bucle, si tiene un archivo en particular que desea ver y eliminar regularmente cuando desea reemplazarlo. Si no siempre tiene el mismo archivo, se puede actualizar un archivo de texto o una tabla de base de datos para que el servicio siempre verifique los nombres de los archivos, y luego realice esa verificación de los procesos y, posteriormente, realice el proceso de eliminación y eliminación, como describí en la siguiente opción Tenga en cuenta que necesitará un nombre de usuario y contraseña de cuenta que tenga privilegios de administrador en la computadora determinada, por supuesto, para realizar la eliminación y finalización de los procesos.

  4. Cuando no sabe si un archivo estará en uso cuando intenta guardarlo, puede cerrar todos los procesos que podrían estar usándolo, como Word, si es un documento de Word, antes de guardarlo.

    Si es local, puede hacer esto:

    ProcessHandler.localProcessKill("winword.exe");

    Si es remoto, puede hacer esto:

    ProcessHandler.remoteProcessKill(computerName, txtUserName, txtPassword, "winword.exe");

    donde txtUserNameestá en la forma de DOMAIN\user.

  5. Digamos que no conoce el nombre del proceso que bloquea el archivo. Entonces, puedes hacer esto:

    List<Process> lstProcs = new List<Process>();
    lstProcs = ProcessHandler.WhoIsLocking(file);
    
    foreach (Process p in lstProcs)
    {
        if (p.MachineName == ".")
            ProcessHandler.localProcessKill(p.ProcessName);
        else
            ProcessHandler.remoteProcessKill(p.MachineName, txtUserName, txtPassword, p.ProcessName);
    }
    

    Tenga en cuenta que filedebe ser la ruta UNC: \\computer\share\yourdoc.docxpara que el Processpueda averiguar en qué computadora está y p.MachineNameser válido.

    A continuación se muestra la clase que utilizan estas funciones, que requiere agregar una referencia System.Management. El código fue escrito originalmente por Eric J .:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Runtime.InteropServices;
    using System.Diagnostics;
    using System.Management;
    
    namespace MyProject
    {
        public static class ProcessHandler
        {
            [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>
            /// Find out what process(es) have a lock on the specified file.
            /// </summary>
            /// <param name="path">Path of the file.</param>
            /// <returns>Processes locking the file</returns>
            /// <remarks>See also:
            /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
            /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
            /// 
            /// </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("Could not begin restart session.  Unable to determine file locker.");
    
                try
                {
                    const int ERROR_MORE_DATA = 234;
                    uint pnProcInfoNeeded = 0,
                        pnProcInfo = 0,
                        lpdwRebootReasons = RmRebootReasonNone;
    
                    string[] resources = new string[] { path }; // Just checking on one resource.
    
                    res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
    
                    if (res != 0) throw new Exception("Could not register resource.");
    
                    //Note: there's a race condition here -- the first call to RmGetList() returns
                    //      the total number of process. However, when we call RmGetList() again to get
                    //      the actual processes this number may have increased.
                    res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
    
                    if (res == ERROR_MORE_DATA)
                    {
                        // Create an array to store the process results
                        RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
                        pnProcInfo = pnProcInfoNeeded;
    
                        // Get the list
                        res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
                        if (res == 0)
                        {
                            processes = new List<Process>((int)pnProcInfo);
    
                            // Enumerate all of the results and add them to the 
                            // list to be returned
                            for (int i = 0; i < pnProcInfo; i++)
                            {
                                try
                                {
                                    processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
                                }
                                // catch the error -- in case the process is no longer running
                                catch (ArgumentException) { }
                            }
                        }
                        else throw new Exception("Could not list processes locking resource.");
                    }
                    else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result.");
                }
                finally
                {
                    RmEndSession(handle);
                }
    
                return processes;
            }
    
            public static void remoteProcessKill(string computerName, string userName, string pword, string processName)
            {
                var connectoptions = new ConnectionOptions();
                connectoptions.Username = userName;
                connectoptions.Password = pword;
    
                ManagementScope scope = new ManagementScope(@"\\" + computerName + @"\root\cimv2", connectoptions);
    
                // WMI query
                var query = new SelectQuery("select * from Win32_process where name = '" + processName + "'");
    
                using (var searcher = new ManagementObjectSearcher(scope, query))
                {
                    foreach (ManagementObject process in searcher.Get()) 
                    {
                        process.InvokeMethod("Terminate", null);
                        process.Dispose();
                    }
                }            
            }
    
            public static void localProcessKill(string processName)
            {
                foreach (Process p in Process.GetProcessesByName(processName))
                {
                    p.Kill();
                }
            }
    
            [DllImport("kernel32.dll")]
            public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);
    
            public const int MOVEFILE_DELAY_UNTIL_REBOOT = 0x4;
    
        }
    }
    
vapcguy
fuente
2

Como han señalado otras respuestas en este hilo, para resolver este error debe inspeccionar cuidadosamente el código, para comprender dónde se bloquea el archivo.

En mi caso, estaba enviando el archivo como un archivo adjunto de correo electrónico antes de realizar la operación de mover.

Entonces, el archivo se bloqueó durante un par de segundos hasta que el cliente SMTP terminó de enviar el correo electrónico.

La solución que adopté fue mover primero el archivo y luego enviar el correo electrónico. Esto resolvió mi problema.

Otra posible solución, como señaló Hudson anteriormente, habría sido deshacerse del objeto después de su uso.

public static SendEmail()
{
           MailMessage mMailMessage = new MailMessage();
           //setup other email stuff

            if (File.Exists(attachmentPath))
            {
                Attachment attachment = new Attachment(attachmentPath);
                mMailMessage.Attachments.Add(attachment);
                attachment.Dispose(); //disposing the Attachment object
            }
} 
Abhishek Poojary
fuente
Si un archivo está en uso File.Move()no funcionará y da el mismo error. Si solo agrego un archivo a un correo electrónico, no creo que sea un error cuando está en uso durante la Attachments.Add()operación porque esto es solo una operación de copia. Si lo hizo por alguna razón, podría copiarlo en un directorio Temp, adjuntar la copia y eliminar el archivo copiado después. Pero no creo, si el OP quiere modificar un archivo y usar eso, que este tipo de solución (de la que no mostró el código, solo la parte adjunta) funcionaría. .Dispose()siempre es una buena idea, pero no es relevante aquí a menos que el archivo se abra en una operación previa.
vapcguy
1

Tuve el siguiente escenario que estaba causando el mismo error:

  • Subir archivos al servidor
  • Luego, elimine los archivos antiguos después de que se hayan cargado

La mayoría de los archivos eran pequeños, sin embargo, algunos eran grandes y, por lo tanto, al intentar eliminarlos, no se pudo acceder al archivo .

No fue fácil de encontrar, sin embargo, la solución fue tan simple como Esperar "a que la tarea complete la ejecución":

using (var wc = new WebClient())
{
   var tskResult = wc.UploadFileTaskAsync(_address, _fileName);
   tskResult.Wait(); 
}
útilBee
fuente
-1

El siguiente código resuelve este problema, pero le sugiero que, en primer lugar, necesite comprender qué está causando este problema y probar la solución que puede encontrar cambiando el código

Puedo dar otra forma de resolver este problema, pero una mejor solución es verificar su estructura de codificación e intentar analizar qué hace que esto suceda, si no encuentra ninguna solución, puede ir con este código a continuación

try{
Start:
///Put your file access code here


}catch (Exception ex)
 {
//by anyway you need to handle this error with below code
   if (ex.Message.StartsWith("The process cannot access the file"))
    {
         //Wait for 5 seconds to free that file and then start execution again
         Thread.Sleep(5000);
         goto Start;
    }
 }
Ceniza
fuente
1
Algunos problemas con este código: 1) si necesita llamar GC.*(), probablemente tenga otros problemas con su código. 2) El mensaje está localizado y es frágil , utilice HRESULT en su lugar. 3) Es posible que desee dormir Task.Delay()(y diez segundos es de alguna manera excesivo en muchos casos). 4) No tiene una condición de salida: este código podría colgarse para siempre. 5) Definitivamente no necesitas gotoaquí. 6) Por Exceptionlo general, atrapar es una mala idea, en este caso también porque ... 6) Si sucede algo más, se está tragando el error.
Adriano Repetti
@AdrianoRepetti mi primer comentario dijo que no use este código, intente encontrar otra solución, esta será la última opción y, de todos modos, si se produce este error, el código se detendrá, pero si está manejando este sistema de error esperará para liberar el archivo y luego comenzará trabajando y no enfrento otros problemas con GC. * mientras uso este código. Ya tengo un sistema operativo con este código, por lo que publiqué puede ser útil para alguien.
Ceniza
Yo diría que no utilizan este código cada vez (a causa de los puntos que he mencionado anteriormente) y yo no lo consideraría este trabajo en un sistema de prod pero eso es sólo mi POV. ¡Siéntete libre de estar en desacuerdo!
Adriano Repetti
1) Utilicé el servicio de Windows donde consumí esto y utilicé GC. * Hasta este momento no enfrento ningún problema con esto2) sí HRESULT puede ser una mejor opción en términos de codificación estándar3) Depende de la situación que pueda darme el tiempo que acabo de editar a 5 segundos 4) de todos modos, es mejor esperar el sistema durante algún tiempo para liberar recursos en lugar de detener el sistema con el error 5) sí, estoy de acuerdo con usted, el manejo de excepciones no siempre es una buena idea, pero si su fragmento de código es pequeño y sabe que aquí puedo manejar excepciones, entonces esta solución es una opción para ti.
Ceniza
Ver también: docs.microsoft.com/en-us/archive/blogs/ricom/… . El ÚNICO caso (y subrayé SOLO) que necesitaba usar GC.Collect()ha sido cuando se trata de algunos objetos COM.
Adriano Repetti