C'è un modo per verificare se un file è in uso?


846

Sto scrivendo un programma in C # che deve accedere ripetutamente a 1 file di immagine. Il più delle volte funziona, ma se il mio computer sta funzionando velocemente, proverà ad accedere al file prima che sia stato salvato nel file system e genererà un errore: "File in uso da un altro processo" .

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


30
Bene, puoi provarlo esaminando tutte le maniglie aperte sul sistema. Tuttavia, poiché Windows è un sistema operativo multitasking, esiste la possibilità che subito dopo aver eseguito il codice per determinare se il file è aperto e si ritiene che non lo sia, un codice di processo inizia a utilizzare quel file, quindi quando si tenta di usalo, ricevi un errore. Ma non c'è nulla di sbagliato nel controllare prima; semplicemente non dare per scontato che non sia in uso quando ne hai davvero bisogno.
BobbyShaftoe,

3
Ma proprio per questo problema specifico; Consiglierei di non esaminare le maniglie dei file e provare solo un numero prestabilito di volte, diciamo 3-5 prima di fallire.
BobbyShaftoe,

Come viene generato questo file immagine? È possibile interrompere / sospendere / mettere in pausa il programma fino al completamento della generazione? Questo è di gran lunga un modo superiore per gestire la situazione. In caso contrario, non penso che tu possa evitare di usare la gestione delle eccezioni.
Catchwa,

L'uso di tutte le eccezioni non è un controllo su alcune ipotesi facendo qualcosa di potenzialmente pericoloso senza deliberatamente escludere la possibilità di un fallimento?
jwg

26
La tua filosofia ha una cattiva comprensione delle eccezioni. La maggior parte delle persone pensa che le eccezioni significhino merda-morte-di-morte-qualcosa-sbagliato-morire-morire-morire. Quando eccezione significa .... eccezione. Significa che si è verificato qualcosa di eccezionale che devi "gestire" (o rendere conto). Forse vuoi continuare a riprovare per l'accesso ai dati, forse l'utente deve sapere che non è possibile ottenere una connessione. cosa fai? Gestisci ConnectionFailedException e informi l'utente, quindi forse smetteranno di provare dopo un'ora e noterai che il cavo è scollegato.
Lee Louviere,

Risposte:


543

NOTA aggiornata su questa soluzione : la verifica con FileAccess.ReadWritenon riuscirà per i file di sola lettura, quindi la soluzione è stata modificata per verificare FileAccess.Read. Mentre questa soluzione funziona perché prova a verificareFileAccess.Read fallirà se il file ha un blocco di scrittura o lettura, tuttavia, questa soluzione non funzionerà se il file non ha un blocco di scrittura o lettura, ovvero è stato aperto (per leggere o scrivere) con accesso a FileShare.Read o FileShare.Write.

ORIGINALE: ho usato questo codice negli ultimi anni e non ho avuto problemi con esso.

Comprendi la tua esitazione sull'uso delle eccezioni, ma non puoi evitarle tutte le volte:

protected virtual bool IsFileLocked(FileInfo file)
{
    try
    {
        using(FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None))
        {
            stream.Close();
        }
    }
    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;
    }

    //file is not locked
    return false;
}

60
Questa è un'ottima soluzione, ma ho un commento: potresti non voler aprire il file con la modalità di accesso FileAccess. Leggi poiché ReadWrite fallirà sempre se il file è di sola lettura.
adeel825,

220
-1. Questa è una risposta scadente, perché il file potrebbe essere bloccato da un altro thread / processo dopo che è stato chiuso in IsFileLocked e prima che il thread abbia la possibilità di aprirlo.
Polyfun,

16
Penso che questa sia un'ottima risposta. Sto usando questo come metodo di estensione alla public static bool IsLocked(this FileInfo file) {/*...*/}.
Manuzor,

54
@ChrisW: potresti chiederti cosa sta succedendo. Non essere allarmato. Sei solo soggetto all'ira della comunità del WTF quotidiano: thedailywtf.com/Comments/…
Pierre Lebeaupin

16
@ChrisW Perché è una brutta cosa. Questa comunità è qui per segnalare risposte buone e cattive. Se un gruppo di professionisti nota che questa è una cosa negativa e si unisce al downvote, il sito è WAI. E prima che diventi negativo, se leggi quell'articolo, dicono "vota la risposta giusta" e non vota quella sbagliata. Vuoi che spieghino anche i loro voti nei commenti. Grazie per avermi fatto conoscere un altro buon sito!
Lee Louviere,

569

Puoi soffrire di una condizione di competizione di thread su questo che ci sono esempi documentati di questo usato come vulnerabilità di sicurezza. Se controlli che il file sia disponibile, ma poi prova a usarlo, potresti lanciarlo a quel punto, che un utente malintenzionato potrebbe usare per forzare e sfruttare il tuo codice.

La tua scommessa migliore è un tentativo di cattura / finalmente che cerca di ottenere l'handle del 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.
}

124
+1. Non esiste un modo sicuro al 100% di "sapere se un file è in uso" perché dopo millisecondi dopo aver effettuato il controllo, il file potrebbe non essere più in uso o viceversa. Invece, basta aprire il file e utilizzarlo se non ci sono eccezioni.
Sedat Kapanoglu,

8
Peccato che .NET non supporti CAS. Qualcosa di simile, TryOpenFile (Ref FileHandle) che restituisce esito positivo / negativo. Dovrebbe esserci sempre una soluzione alternativa non fare affidamento solo sulla gestione delle eccezioni. Mi chiedo come fa Microsoft Office.
TamusJRoyce,

2
La cosa chiave da capire qui è che questa API sta semplicemente usando l'API di Windows per ottenere un handle di file. Pertanto, devono tradurre il codice di errore ricevuto dall'API C e racchiuderlo in un'eccezione da generare. Abbiamo una gestione delle eccezioni in .Net, quindi perché non usarla. In questo modo è possibile scrivere un percorso di inoltro pulito nel codice e lasciare la gestione degli errori in un percorso di codice separato.
Spence

36
L'istruzione using serve a garantire che lo stream sia chiuso dopo che ho finito. Penso che scoprirai che using () {} è meno caratteri di provare {} infine {obj.Dispose ()}. Scoprirai inoltre che ora devi dichiarare il riferimento al tuo oggetto al di fuori dell'istruzione using, che è più digitante. Se hai un'interfaccia esplicita, dovresti anche trasmettere. Infine, devi disporre al più presto e la logica finally può avere l'interfaccia utente o qualsiasi altra azione di lunga durata che ha poco a che fare con la chiamata di IDispose. </rant>
Spence,

2
ciò non nega il fatto che devi dichiarare il tuo oggetto al di fuori del tentativo e devi chiamare esplicitamente dispose, che l'utilizzo fa per te e significa la stessa cosa.
Spence

92

Usa 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, ti 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;
}

Provalo tu stesso:

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

8
Vorrei votare se non ci fossero così tanti "numeri magici" lì dentro en.wikipedia.org/wiki/Magic_number_(programming)
Kris

3
Mi riferivo ai confronti di ErrorCode, non ai bit shift. anche se ora lo dici ...
Kris

1
Il tuo Catch dovrebbe essere attivo IOException, anziché in generale Exceptione quindi un test sul tipo.
Askolein,

3
@JeremyThompson purtroppo hai messo lo specifico IOExceptiondopo quello generale. Quello generale prenderà tutto ciò che passa e lo specifico IOExceptionsarà sempre solo. Basta scambiare i due.
Askolein,

Mi piace questa soluzione. Un altro suggerimento: dentro la cattura come un altro al if (IsFileLocked (ex)) vorrei lanciare ex. Questo gestirà quindi il caso in cui il file non esiste (o qualsiasi altra IOException) generando l'eccezione.
shindigo,

7

Usa l'eccezione come previsto. Accettare che il file sia in uso e riprovare, ripetutamente fino al completamento dell'azione. Questo è anche il più efficace perché non sprechi alcun ciclo controllando lo stato prima di agire.

Utilizzare la funzione seguente, 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);
}

6

Forse potresti usare un FileSystemWatcher e guardare l'evento Changed.

Non l'ho usato da solo, ma potrebbe valere la pena provare. Se il filesystemwatcher risulta essere un po 'pesante per questo caso, sceglierei il ciclo try / catch / sleep.


1
L'uso di un FileSystemWatcher non aiuta, poiché gli eventi Creati e Modificati generano all'inizio della creazione / modifica di un file. Anche i file di piccole dimensioni richiedono più tempo per essere scritti e chiusi dal sistema operativo rispetto all'applicazione .NET per eseguire il callback FileSystemEventHandler. Questo è così triste, ma non c'è altra opzione che stimare il tempo di attesa prima di accedere al file o eseguire cicli di eccezione ...

FileSystemWatcher non gestisce molte modifiche allo stesso tempo molto bene, quindi fai attenzione.
Ben F,

1
A proposito, avete notato ragazzi mentre eseguite il debug e guardate i thread che MS chiama il proprio "FileSystemWather" di FSW? Che cos'è un wather comunque?
Devlord,

Ho riscontrato esattamente questo problema perché un servizio Windows con FileSystemWatcher tenta di leggere il file prima che il processo lo chiuda.
Freedeveloper

6

Puoi restituire un'attività che ti dà uno stream non appena diventa disponibile. È una soluzione semplificata, ma è un buon punto di partenza. È 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();
    }
}

Puoi usare questo stream come al solito:

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

3
Quanti secondi prima che uno stack trabocchi dalla ricorsione in GetStreamAsync()?
Bloke CAD,

@CADbloke, hai sollevato un ottimo punto. In effetti il ​​mio campione potrebbe avere un'eccezione di overflow dello stack, nel caso in cui un file non sia disponibile per un lungo periodo. In relazione a questa risposta stackoverflow.com/questions/4513438/… , potrebbe sollevare l'eccezione in 5 ore.
Ivan Branets,

In relazione ai casi d'uso, è preferibile generare un'eccezione I / O se diciamo che 10 tentativi di leggere il file non sono riusciti. L'altra strategia potrebbe aumentare il tempo di attesa per un secondo dopo 10 tentativi falliti. Puoi anche usare un mix di entrambi.
Ivan Branets,

Vorrei (e fare) semplicemente avvisare l'utente che il file è bloccato. In genere lo hanno bloccato da soli, quindi probabilmente faranno qualcosa al riguardo. O no.
Bloke CAD,

In alcuni casi, è necessario utilizzare i criteri di tentativo poiché un file potrebbe non essere ancora pronto. Immagina un'applicazione desktop per scaricare immagini in qualche cartella temporanea. L'applicazione avvia il download e allo stesso tempo si apre questa cartella in Esplora file. Windows vuole creare immediatamente un'anteprima e bloccare il file. Allo stesso tempo, la tua app tenta di sostituire l'immagine bloccata in un altro posto. Riceverai un'eccezione se non utilizzi la politica dei tentativi.
Ivan Branets,

4

l'unico modo che conosco è usare l'API di blocco esclusivo Win32 che non è troppo veloce, ma esistono esempi.

Molte persone, per una semplice soluzione a questo, semplicemente per provare / catturare / dormire loop.


1
Non è possibile utilizzare questa API senza prima aprire il file, a quel punto non è più necessario.
Harry Johnston,

1
Il file di Schrodinger.
TheLastGIS il

4
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 sia di aiuto!


10
Il controllo effettivo che esegui va bene; inserirla in una funzione è fuorviante. NON si desidera utilizzare una funzione come questa prima di aprire un file. All'interno della funzione, il file viene aperto, verificato e chiuso. Quindi il programmatore ASSUME che il file sia ANCORA ok da usare e prova ad aprirlo per l'uso. Ciò è negativo perché potrebbe essere utilizzato e bloccato da un altro processo messo in coda per aprire questo file. Tra la prima volta che è stato aperto (per il controllo) e la seconda volta che è stato aperto (per l'uso), il sistema operativo potrebbe aver pianificato il processo e potrebbe essere in esecuzione un altro processo.
Lakey,

3

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

  1. Non funzionerà per i file che sono stati aperti con una modalità di condivisione in scrittura
  2. Ciò non tiene conto dei problemi di threading, quindi sarà necessario bloccarlo o gestirli separatamente.

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

Ha ancora lo stesso problema della risposta accettata: ti dice solo se il file è stato bloccato da un altro processo in un determinato momento , il che non è un'informazione utile. Quando la funzione è tornata, il risultato potrebbe essere già obsoleto!
Harry Johnston,

1
è vero, si può solo controllare in un dato momento (o iscriversi agli eventi), il vantaggio di questo approccio rispetto alla soluzione accettata è che può controllare un attributo di sola lettura e un blocco di scrittura e non restituire un falso positivo.
Rboy,

3

Oltre a lavorare con 3 righe e solo per riferimento: se vuoi le informazioni complete , c'è un piccolo progetto su Microsoft Dev Center:

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

Dall'introduzione:

Il codice di esempio C # sviluppato in .NET Framework 4.0 aiuterebbe a scoprire qual è il processo che ha un blocco su un file. La funzione RmStartSession inclusa in rstrtmgr.dll è stata utilizzata per creare una sessione del gestore riavvio e, in base al risultato restituito, viene creata una nuova istanza dell'oggetto Win32Exception. Dopo aver registrato le risorse per una sessione di Gestione riavvio via RmRegisterRescources funzione, RmGetList funzione viene richiamata per verificare quali sono le applicazioni utilizzano un particolare file enumerando il RM_PROCESS_INFO array.

Funziona connettendosi alla "Restart Manager Session".

Restart Manager utilizza l'elenco delle risorse registrate con la sessione per determinare quali applicazioni e servizi devono essere chiusi e riavviati. Le risorse possono essere identificate da nomi di file, nomi abbreviati del servizio o strutture RM_UNIQUE_PROCESS che descrivono le applicazioni in esecuzione.

Potrebbe essere un po 'troppo ingegnerizzato per le tue esigenze particolari ... Ma se è quello che vuoi, vai avanti e prendi il vs-project.


2

Nella mia esperienza, di solito vuoi farlo, quindi "proteggere" i tuoi file per fare qualcosa di elegante e quindi utilizzare i file "protetti". Se hai un solo file che vuoi usare in questo modo, puoi usare il trucco spiegato nella risposta di Jeremy Thompson. Tuttavia, se si tenta di eseguire questa operazione su molti file (ad esempio, quando si scrive un programma di installazione), ci si sente un po 'feriti.

Un modo molto elegante che può essere risolto è usando il fatto che il tuo file system non ti permetterà di cambiare il nome di una cartella se uno dei file lì viene utilizzato. Mantieni la cartella nello stesso file system e funzionerà come un incantesimo.

Nota che dovresti essere consapevole dei modi ovvi in ​​cui questo può essere sfruttato. Dopotutto, i file non saranno bloccati. Inoltre, tenere presente che esistono altri motivi per cui l' Moveoperazione potrebbe non riuscire. Ovviamente la corretta gestione degli errori (MSDN) può essere d'aiuto 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 rimarrei con il suggerimento di blocco pubblicato da Jeremy Thompson.


Ciao, poiché l'ordine delle risposte cambia, puoi chiarire quale post sopra intendi per i lettori di questo famoso QA. Grazie.
Jeremy Thompson,

1
@JeremyThompson Hai ragione, grazie, modificherò il post. Utilizzerei la soluzione da te, principalmente a causa del tuo uso corretto FileSharee del controllo di una serratura.
atlaste,

2

Ecco un po 'di codice che, per quanto posso dire, fa la stessa cosa della 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 sia più robusto farlo nel modo seguente:

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

2

Puoi utilizzare la mia libreria per accedere ai file da più app.

Puoi installarlo dal nuget: Install-Package Xabe.FileLock

Se vuoi maggiori informazioni a riguardo controlla https://github.com/tomaszzmuda/Xabe.FileLock

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

Il metodo fileLock.Acquire restituirà true solo se è possibile bloccare il file esclusivo per questo oggetto. Ma l'app che carica il file deve farlo anche nel blocco dei file. Se l'oggetto è inaccessibile, il metodo restituisce false.


1
Ti preghiamo di non pubblicare solo alcuni strumenti o librerie come risposta. Almeno dimostrare come risolve il problema nella risposta stessa.
paper1111,

Aggiunta demo :) Siamo spiacenti @ paper1111
Tomasz Żmuda

1
Richiede tutti i processi che utilizzano il file per collaborare. È improbabile che sia applicabile al problema originale dei PO.
Harry Johnston,

2

Una volta avevo bisogno di caricare PDF in un archivio di backup online. Ma il backup fallirebbe se l'utente avesse il file aperto in un altro programma (come un lettore PDF). Nella mia fretta, ho provato alcune delle risposte migliori in questa discussione ma non sono riuscito a farle funzionare. Quello che ha funzionato per me è stato cercare di spostare il file PDF nella sua directory . Ho scoperto che ciò non avrebbe esito positivo se il file fosse stato aperto in un altro programma e se lo spostamento avesse esito positivo non sarebbe stata necessaria alcuna operazione di ripristino come se fosse stata spostata in una directory separata. Voglio pubblicare la mia soluzione di base nel caso in cui possa essere utile per casi d'uso specifici di 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
}

0

Sono interessato a vedere se questo innesca eventuali riflessi WTF. Ho un processo che crea e successivamente avvia un documento PDF da un'app console. 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'app genererebbe un'eccezione e morirà. Ciò si è verificato piuttosto frequentemente perché i nomi dei file si basano sui numeri delle offerte di vendita.

Invece di fallire in un modo così ingrato, ho deciso di fare affidamento sul controllo delle versioni dei file con incremento automatico:

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 'più di attenzione può essere data al catchblocco per assicurarsi che stia rilevando le IOException corrette. Probabilmente cancellerò anche l'archiviazione dell'app all'avvio poiché questi file devono comunque essere temporanei.

Mi rendo conto che questo va oltre lo scopo della questione del PO di controllare semplicemente se il file è in uso, ma questo era davvero il problema che stavo cercando di risolvere quando sono arrivato qui, quindi forse sarà utile a qualcun altro.


0

Qualcosa del genere sarebbe di 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)
    {

    }
}

-2

Prova a spostare / copiare il file in una directory temporanea. Se puoi, non ha blocchi e puoi tranquillamente lavorare nella directory temporanea senza ottenere blocchi. Altrimenti prova a spostarlo di nuovo in x secondi.


@jcolebrand blocca cosa? quello che hai copiato? O quello che hai messo nella directory temporanea?
Cullub,

5
se copi il file, aspettandoti che nessun altro ci stia lavorando e utilizzerai il file temporaneo, quindi qualcuno lo bloccherà subito dopo averlo copiato, quindi potresti potenzialmente perdere i dati.
jcolebrand,

-3

Uso questa soluzione alternativa, ma ho un intervallo di tempo tra quando controllo il blocco dei file con la funzione IsFileLocked e quando apro il file. In questo periodo di tempo alcuni altri thread possono aprire il file, quindi otterrò IOException.

Quindi, ho aggiunto un codice aggiuntivo per questo. Nel mio caso voglio caricare 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 qualcosa? Forse non ho dovuto usare la funzione IsFileBeingUsed?

Grazie


4
cos'è IsFileBeingUsed? codice sorgente su IsFileBeingUsed?
Kiquenet,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.