Domanda

Sto scrivendo un programma in C # che deve accedere ripetutamente 1 file immagine. Il più delle volte funziona, ma se il mio computer di correre veloce, cercherà di accedere al file prima che sia stato salvato di nuovo al file system e genera un errore: "File in uso da un altro processo" .

Mi piacerebbe trovare un modo per aggirare questo, ma tutto il mio Googling ha ceduto solo la creazione di controlli utilizzando la gestione delle eccezioni. Questo è contro la mia religione, quindi mi chiedevo se qualcuno ha un modo migliore di farlo?

È stato utile?

Soluzione

nota di update su questa soluzione : Verifica con FileAccess.ReadWrite non riuscirà a file di sola lettura per cui la soluzione è stata modificata per controllare con FileAccess.Read. Anche se questa soluzione funziona perché cercando di verificare con FileAccess.Read non riuscirà se il file ha una scrittura o lettura blocco su di esso, però, questa soluzione non funziona se il file non dispone di una scrittura o lettura blocco su di esso, vale a dire che è stato aperto (per la lettura o la scrittura) con FileShare.Read o FileShare.Write accesso.

ORIGINALE: Ho usato questo codice per il passato parecchi anni, e non ho avuto problemi con esso.

Capire la vostra esitazione sull'utilizzo delle eccezioni, ma non si può evitare di loro tutto il tempo:

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

Altri suggerimenti

È possibile soffrire di una condizione di competizione thread su questo che si sono documentati esempi di questo essere utilizzato come una vulnerabilità di sicurezza. Se si controlla che il file è disponibile, ma poi cercare di usarlo si potrebbe buttare a quel punto, che un utente malintenzionato potrebbe utilizzare per imporre e sfruttare nel codice.

La cosa migliore è una cattura try / finally che cerca di ottenere l'handle di file.

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

Utilizza questo per verificare se un file è bloccato:

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

Per motivi di prestazioni vi consiglio di leggere il contenuto del file nella stessa operazione. Ecco alcuni esempi:

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

Provate voi stessi:

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

Forse si potrebbe utilizzare un FileSystemWatcher e orologio per l'evento Changed.

Non ho usato io stesso, ma forse vale la pena un colpo. Se il FileSystemWatcher risulta essere un po 'pesante per questo caso, vorrei andare per il / catch / loop prova di sonno.

Basta usare l'eccezione come previsto. Accettare che il file è in uso e riprovare, finché non è stata completata l'azione. Questo è anche il più efficace perché non sprecare cicli di verifica dello stato prima di agire.

Utilizzare la funzione di seguito, ad esempio

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

metodo riutilizzabile che scade dopo 2 secondi

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

l'unico modo che conosco è quello di utilizzare il Win32 API esclusiva di blocco che non è troppo veloce, ma esistono esempi.

La maggior parte delle persone, per una semplice soluzione a questo, semplicemente per try / catch / cicli del sonno.

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

Spero che questo aiuti!

È possibile restituire un compito che ti dà un flusso, non appena sarà disponibile. Si tratta di una soluzione semplificata, ma è un buon punto di partenza. E 'thread-safe.

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

È possibile utilizzare questo flusso, come al solito:

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

Le risposte accettate sopra soffrono il problema per cui se il file è stato aperto per la scrittura di una modalità FileShare.Read o se il file ha un attributo di sola lettura del codice non funziona. Questa soluzione modificato funziona più in modo affidabile, con due cose da tenere a mente (come vale per la soluzione accettata anche):

  1. Non funzionerà per i file che è stato aperto con una modalità di condivisione di scrittura
  2. Questo non prende in considerazione i problemi di threading quindi sarà necessario per bloccare il basso o gestire problemi di threading separatamente.

Tenendo in mente quanto sopra, questo controlla se il file è bloccato per la scrittura o bloccato per impedire la lettura :

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

Oltre a lavorare 3 battute e solo per riferimento: Se si desidera che il piena regola Informazioni - c'è un piccolo progetto su Microsoft Dev Center:

https://code.msdn.microsoft .com / windowsapps / How-to-know-il-processo-704839f4

Dalla Introduzione:

  

Il codice di esempio # C sviluppato in .NET Framework 4.0 aiuterebbe a   scoprire che è il processo che sta avendo un blocco su un file.    RmStartSession la funzione che è incluso nel rstrtmgr.dll è stato   utilizzato per creare una sessione di riavvio manager e in base alla dichiarazione   I risultati viene creata una nuova istanza di oggetto Win32Exception. Dopo   registrando le risorse per una sessione di Gestione riavvio via    RmRegisterRescources la funzione, RmGetList la funzione viene richiamata per controllare   Quali sono le applicazioni utilizzano un particolare file enumerando   la RM_PROCESS_INFO array.

Si lavora collegandosi al "Gestore della sessione Riavvia".

  

Il Restart Manager utilizza l'elenco delle risorse registrata con la sessione di   determinano quali applicazioni e servizi devono essere arrestati e riavviati.    Le risorse possono essere identificati da nomi di file, nomi brevi di servizi, o   strutture RM_UNIQUE_PROCESS che descrivono le applicazioni in esecuzione.

Potrebbe essere un po ' overengineered per le vostre esigenze particolari ... Ma se questo è ciò che vuole, andare avanti e afferrare il vs-progetto.

Un po 'di codice che, per quanto posso raccontare meglio fa la stessa cosa come la risposta accettata, ma con meno codice:

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

Tuttavia penso che è più robusto di farlo nel seguente modo:

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

È possibile utilizzare la mia libreria per l'accesso ai file da più applicazioni.

È possibile installarlo dal NuGet: Installare-Pacchetto Xabe.FileLock

Se volete maggiori informazioni su di esso controlla https://github.com/tomaszzmuda/Xabe.FileLock

ILock fileLock = new FileLock(file);
if(fileLock.Acquire(TimeSpan.FromSeconds(15), true))
{
    using(fileLock)
    {
        // file operations here
    }
}

metodo fileLock.Acquire restituisce vero solo se in grado di bloccare il file in esclusiva per questo oggetto. Ma app che il caricamento del file deve farlo in blocco di file troppo. Se oggetto è Metod inaccessibile restituisce false.

Nella mia esperienza, di solito si vuole fare questo, allora 'proteggere' i file di fare qualcosa di fantasia e quindi utilizzare i file 'protetti'. Se si dispone di un solo file che si desidera utilizzare in questo modo, è possibile utilizzare il trucco che è spiegato nella risposta da Jeremy Thompson. Tuttavia, se si tenta di fare questo su un sacco di file (ad esempio, ad esempio, quando si scrive un programma di installazione), ci si trova per un po 'di dolore.

Un modo molto elegante questo può essere risolto è quello di utilizzare il fatto che il file system non permetterà di cambiare il nome della cartella, se uno dei file non è in uso. Mantenere la cartella nello stesso file system e che funzionerà come un fascino.

Do atto che si dovrebbe essere consapevoli dei modi ovvi Ciò può essere sfruttato. Dopo tutto, i file non saranno bloccati. Inoltre, essere consapevoli che ci sono altri motivi che possono causare l'operazione Move a fallire. Ovviamente una corretta gestione degli errori (MSDN) può aiutare qui.

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

Per i singoli file mi piacerebbe restare con il suggerimento di blocco inviato da Jeremy Thompson.

Sono interessato a vedere se questo fa scattare eventuali riflessi WTF. Ho un processo che crea e poi lancia un documento PDF da una console app. Tuttavia, avevo a che fare con una fragilità in cui se l'utente dovesse eseguire il processo più volte, generando lo stesso file senza prima chiudere il file generato in precedenza, l'applicazione sarebbe un'eccezione e morire. Questo è stato un evento piuttosto frequente, perché i nomi di file si basano su numeri quote di vendita.

Invece di non aver in un modo così sgraziata, ho deciso di affidarsi a delle versioni dei file auto-increment:

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

Probabilmente un po 'di cura può essere dato al blocco catch per assicurare che sto cattura il corretto IOException (s). Io probabilmente anche chiaro l'archiviazione applicazioni all'avvio poiché questi file sono destinati ad essere temporaneo in ogni modo.

Mi rendo conto che va al di là della portata della domanda del PO di semplicemente controllando se il file è in uso, ma questo era davvero il problema che stavo cercando di risolvere quando sono arrivato qui così forse sarà utile a qualcun altro.

Sarebbe qualcosa di simile a questo aiuto?

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)
    {

    }
}

Una volta ho bisogno di caricare i file PDF in un archivio di backup online. Ma il backup fallirebbe se l'utente avesse il file aperto in un altro programma (ad esempio lettore PDF). Nella mia fretta, ho cercato alcuni dei migliori risposte in questa discussione, ma non sono riuscito a farli funzionare. Ciò che ha funzionato per me cercavo di spostare il file PDF in la propria directory . Ho trovato che questo fallirebbe se il file è stato aperto in un altro programma, e se la mossa fosse successo non ci sarebbe stato il ripristino-operazione necessaria in quanto non ci sarebbe se fosse spostato in una directory separata. Voglio inviare la mia soluzione di base nel caso in cui può essere utile per specifici casi d'uso degli altri.

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
}

Prova a spostare / copiare il file in una directory temporanea. Se è possibile, non ha serratura e si può lavorare in sicurezza nella directory Temp senza ottenere serrature. Altrimenti basta provare a muoversi di nuovo dopo x secondi.

I utilizzare questa soluzione, ma ho un periodo tra quando controllo il file di blocco con la funzione IsFileLocked e quando ho aperto il file. In questo periodo qualche altro thread può aprire il file, in modo che io otterrà IOException.

Così, ho aggiunto il codice extra per questo. Nel mio caso voglio carico 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!!!!!");
            }
        }

Cosa ne pensi? Posso cambiare qualche cosa? Forse io non ho dovuto usare la funzione IsFileBeingUsed a tutti?

Grazie

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top