come puoi facilmente verificare se l'accesso è negato per un file in .NET?


100

Fondamentalmente, vorrei controllare se ho i diritti per aprire il file prima di provare ad aprirlo effettivamente; Non voglio usare un try / catch per questo controllo a meno che non sia necessario. Esiste una proprietà di accesso ai file che posso controllare in anticipo?


2
Didascalia quando ho cambiato il tag: "im correcting". Nessun scherzo.
Joel Coehoorn,

6
Concordato: vorrei che ci fosse un TryOpen (cioè il pattern Try-Parse).
Tristan

Risposte:


157

L'ho fatto innumerevoli volte in passato e quasi ogni volta che l'ho fatto ho sbagliato anche solo a tentare.

I permessi dei file (anche l'esistenza del file) sono volatili : possono cambiare in qualsiasi momento. Grazie alla legge di Murphy, questo include in particolare il breve periodo tra il momento in cui controlli il file e il momento in cui provi ad aprirlo. Un cambiamento è ancora più probabile se ti trovi in ​​un'area in cui sai che devi prima controllare. Eppure, stranamente, non accadrà mai nei tuoi ambienti di test o di sviluppo, che tendono ad essere abbastanza statici. Ciò rende il problema difficile da rintracciare in seguito e rende facile per questo tipo di bug entrare in produzione.

Ciò significa che devi ancora essere in grado di gestire l'eccezione se i permessi o l'esistenza dei file sono cattivi, nonostante il tuo controllo. È richiesto il codice di gestione delle eccezioni , indipendentemente dal fatto che si verifichino o meno le autorizzazioni del file in anticipo. Il codice di gestione delle eccezioni fornisce tutte le funzionalità di verifica dell'esistenza o delle autorizzazioni. Inoltre, sebbene i gestori di eccezioni come questo siano noti per essere lenti, è importante ricordare che l'i / o del disco è ancora più lento ... molto più lento ... e la chiamata alla funzione .Exists () o il controllo delle autorizzazioni forzerà un viaggio aggiuntivo il file system.

In sintesi, un controllo iniziale prima di provare ad aprire il file è ridondante e dispendioso. Non c'è alcun vantaggio aggiuntivo rispetto alla gestione delle eccezioni, in realtà danneggerà, non aiuterà, le tue prestazioni, aggiunge costi in termini di più codice che deve essere mantenuto e può introdurre piccoli bug nel tuo codice. Non c'è proprio alcun vantaggio nel fare il controllo iniziale. Invece, la cosa corretta qui è provare ad aprire il file e mettere i tuoi sforzi in un buon gestore di eccezioni se fallisce. Lo stesso vale anche se stai solo controllando se il file esiste o meno. Questo ragionamento si applica a qualsiasi risorsa volatile.


5
Esattamente. Questo è un classico esempio di una condizione di gara.
Powerlord

3
korro: devi essere in grado di gestire i permessi sbagliati in caso di fallimento comunque, e questo rende il controllo iniziale ridondante e dispendioso.
Joel Coehoorn

2
Un controllo iniziale può aiutare a gestire con garbo gli errori specifici comuni: guardare avanti è spesso più facile che abbinare attributi di eccezione particolari a cause specifiche. La prova / cattura resta comunque obbligatoria.
Peterchen

5
Questa risposta non risponde alla domanda "come verificare se ho i diritti per aprire il file" in qualche istanza prima di provare ad aprirlo. Il caso potrebbe benissimo essere che se l'autorizzazione non è consentita in quell'istanza, il software non tenterà di leggere il file, anche se l'autorizzazione potrebbe essere concessa subito dopo aver verificato le autorizzazioni.
Triynko

5
Non importa se le autorizzazioni sono volatili, quando ti interessa solo cosa sono in quell'istante. L'errore dovrebbe sempre essere gestito, ma se controlli un'autorizzazione di lettura e non è presente, potresti voler saltare la lettura del file, anche se è possibile che un secondo dopo tu possa avere accesso. Devi tracciare la linea da qualche parte.
Triynko

25

Suggerimento rapido per chiunque venga qui con un problema simile:

Fai attenzione alle app di sincronizzazione web come DropBox. Ho appena passato 2 ore a pensare che l'istruzione "using" (Dispose pattern) non funzionasse in .NET.

Alla fine ho capito che Dropbox legge e scrive continuamente file in background, per sincronizzarli.

Indovina dove si trova la mia cartella Progetti di Visual Studio? Ovviamente nella cartella "Il mio Dropbox".

Pertanto, mentre eseguivo la mia applicazione in modalità Debug, DropBox accedeva continuamente anche ai file che stava leggendo e scrivendo per essere sincronizzati con il server DropBox. Ciò ha causato i conflitti di blocco / accesso.

Quindi almeno ora so che ho bisogno di una funzione di apertura file più robusta (ad esempio TryOpen () che farà più tentativi). Sono sorpreso che non sia già una parte integrata del framework.

[Aggiornare]

Ecco la mia funzione di aiuto:

/// <summary>
/// Tries to open a file, with a user defined number of attempt and Sleep delay between attempts.
/// </summary>
/// <param name="filePath">The full file path to be opened</param>
/// <param name="fileMode">Required file mode enum value(see MSDN documentation)</param>
/// <param name="fileAccess">Required file access enum value(see MSDN documentation)</param>
/// <param name="fileShare">Required file share enum value(see MSDN documentation)</param>
/// <param name="maximumAttempts">The total number of attempts to make (multiply by attemptWaitMS for the maximum time the function with Try opening the file)</param>
/// <param name="attemptWaitMS">The delay in Milliseconds between each attempt.</param>
/// <returns>A valid FileStream object for the opened file, or null if the File could not be opened after the required attempts</returns>
public FileStream TryOpen(string filePath, FileMode fileMode, FileAccess fileAccess,FileShare fileShare,int maximumAttempts,int attemptWaitMS)
{
    FileStream fs = null;
    int attempts = 0;

    // Loop allow multiple attempts
    while (true)
    {
        try
        {
            fs = File.Open(filePath, fileMode, fileAccess, fileShare);

            //If we get here, the File.Open succeeded, so break out of the loop and return the FileStream
            break;
        }
        catch (IOException ioEx)
        {
            // IOExcception is thrown if the file is in use by another process.

            // Check the numbere of attempts to ensure no infinite loop
            attempts++;
            if (attempts > maximumAttempts)
            {
                // Too many attempts,cannot Open File, break and return null 
                fs = null;
                break;
            }
            else
            {
                // Sleep before making another attempt
                Thread.Sleep(attemptWaitMS);

            }

        }

    }
    // Reutn the filestream, may be valid or null
    return fs;
}

3
@ Ash, penso che tu non abbia letto correttamente la domanda. Vuole evitare di provare a catturare.
Ravisha

10
@ Ravisha, hai persino letto la risposta più votata da Joel? Come dice Joel, "Quello che fai invece è provare ad aprire il file e gestire l'eccezione se fallisce" . Per favore, non sottovalutare solo perché non ti piace il fatto che qualcosa non possa essere evitato.
Ash

Grazie per il codice! Una cosa, potrebbe essere meglio usare, ad esempio, vedere la risposta di Tazeem qui
Cel

Dato che restituisci il filestream, allora usingdovrebbe essere usato dal chiamante però ...
Cel

@ Cel - usingnon funzionerà qui. Alla fine del blocco di utilizzo, fsverrà forzata la chiusura. Darai al chiamante un filestream CHIUSO (così inutile)!
ToolmakerSteve

4

Ecco la soluzione che stai cercando

var fileIOPermission = new FileIOPermission(FileIOPermissionAccess.Read,
                                            System.Security.AccessControl.AccessControlActions.View,
                                            MyPath);

if (fileIOPermission.AllFiles == FileIOPermissionAccess.Read)
{
    // Do your thing here...
}

questo crea una nuova autorizzazione di lettura basata sulla visualizzazione del percorso di tutti i file, quindi controlla se è uguale all'accesso ai file in lettura.


3

Primo, quello che ha detto Joel Coehoorn.

Inoltre: dovresti esaminare i presupposti che stanno alla base del tuo desiderio di evitare di usare try / catch a meno che non sia necessario. Il motivo tipico per evitare la logica che dipende dalle eccezioni (la creazione di Exceptionoggetti non funziona correttamente) probabilmente non è rilevante per il codice che apre un file.

Suppongo che se stai scrivendo un metodo che popola un List<FileStream>aprendo ogni file in una sottostruttura di directory e ti aspettavi che un gran numero di essi fosse inaccessibile, potresti voler controllare i permessi dei file prima di provare ad aprire un file in modo che non l'hai fatto ottenere troppe eccezioni. Ma gestiresti comunque l'eccezione. Inoltre, probabilmente c'è qualcosa di terribilmente sbagliato nel design del tuo programma se stai scrivendo un metodo che fa questo.


-1
public static bool IsFileLocked(string filename)
        {
            bool Locked = false;
            try
            {
                FileStream fs =
                    File.Open(filename, FileMode.OpenOrCreate,
                    FileAccess.ReadWrite, FileShare.None);
                fs.Close();
            }
            catch (IOException ex)
            {
                Locked = true;
            }
            return Locked;
        }

-3
public static FileStream GetFileStream(String filePath, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, ref int attempts, int attemptWaitInMilliseconds)
{            
    try
    {
         return File.Open(filePath, fileMode, fileAccess, fileShare);
    }
    catch (UnauthorizedAccessException unauthorizedAccessException)
    {
        if (attempts <= 0)
        {
            throw unauthorizedAccessException;
        }
        else
        {
            Thread.Sleep(attemptWaitInMilliseconds);
            attempts--;
            return GetFileStream(filePath, fileMode, fileAccess, fileShare, ref attempts, attemptWaitInMilliseconds);
        }
    }
}

8
-1: usa "lancia;" non "lanciare unauthorizedAccessException;". Stai perdendo la traccia dello stack.
John Saunders

Perché è attemptspassato per ref? Non ha senso. Nemmeno il test per <=invece di solo ==.
Konrad Rudolph

1
@ John: beh, in questo caso è desiderabile perdere la traccia dello stack (profondamente annidata) della chiamata ricorsiva, quindi penso che in questo caso throw exsia effettivamente la cosa giusta da fare.
Konrad Rudolph

2
@Konrad: @Rudzitis: sto cambiando la mia ragione per -1. È peggio che rovinare la pila con "lancia ex". Stai rovinando lo stack inducendo artificialmente livelli di stack extra attraverso la ricorsione in un momento in cui la profondità dello stack è davvero importante. Questo è un problema iterativo, non ricorsivo.
John Saunders
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.