it-swarm.com.de

Gibt es eine Möglichkeit zu überprüfen, ob eine Datei verwendet wird?

Ich schreibe ein Programm in C #, das wiederholt auf eine Bilddatei zugreifen muss. Meistens funktioniert es, aber wenn mein Computer schnell läuft, versucht er, auf die Datei zuzugreifen, bevor sie wieder im Dateisystem gespeichert wurde, und gibt einen Fehler aus: "Datei wird von einem anderen Prozess verwendet" .

Ich würde gerne einen Weg finden, um das zu umgehen, aber mein Googling hat nur die Erstellung von Prüfungen durch Ausnahmebehandlung ermöglicht. Das ist gegen meine Religion, also habe ich mich gefragt, ob jemand einen besseren Weg hat, dies zu tun?

743
Dawsy

Aktualisierter HINWEIS zu dieser Lösung : Die Überprüfung mit FileAccess.ReadWrite schlägt für schreibgeschützte Dateien fehl. Daher wurde die Lösung so geändert, dass sie mit FileAccess.Read überprüft wird. Diese Lösung funktioniert zwar, weil der Versuch, mit FileAccess.Read zu suchen, fehlschlägt, wenn für die Datei eine Schreib- oder Lesesperre festgelegt ist. Diese Lösung funktioniert jedoch nicht, wenn die Datei keine Schreib- oder Lesesperre enthält (zum Lesen oder Schreiben) mit FileShare.Read oder FileShare.Write-Zugriff geöffnet.

ORIGINAL: Ich habe diesen Code in den letzten Jahren verwendet, und ich hatte keine Probleme damit.

Verstehen Sie Ihr Zögern bei der Verwendung von Ausnahmen, aber Sie können sie nicht immer vermeiden:

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

Sie können an einer Thread-Race-Bedingung leiden, die dokumentiert ist und als Sicherheitsanfälligkeit verwendet wird. Wenn Sie prüfen, ob die Datei verfügbar ist, und dann versuchen, sie zu verwenden, könnten Sie an dieser Stelle werfen, die ein böswilliger Benutzer dazu verwenden könnte, Ihren Code zu erzwingen und zu nutzen.

Ihre beste Wette ist ein Try-Catch, der versucht, die Datei in die Hand zu bekommen.

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

Verwenden Sie diese Option, um zu überprüfen, ob eine Datei gesperrt ist:

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

Aus Leistungsgründen empfehle ich Ihnen, den Dateiinhalt in derselben Operation zu lesen. Hier sind einige Beispiele:

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

Probieren Sie es selbst aus:

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

Vielleicht könnten Sie einen FileSystemWatcher verwenden und auf das Changed-Ereignis achten.

Ich habe das selbst nicht benutzt, aber es lohnt sich vielleicht. Wenn sich der filesystemwatcher für diesen Fall als etwas schwer herausstellt, würde ich die try/catch/sleep-Schleife wählen.

6
Karl Johan

Verwenden Sie einfach die Ausnahme wie beabsichtigt. Akzeptieren Sie, dass die Datei verwendet wird, und versuchen Sie es erneut, bis die Aktion abgeschlossen ist. Dies ist auch am effizientesten, da Sie vor dem Handeln keine Zyklen für die Überprüfung des Status verschwenden.

Verwenden Sie beispielsweise die folgende Funktion

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

Wiederverwendbare Methode, die nach 2 Sekunden abläuft

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

Sie können eine Aufgabe zurückgeben, die Ihnen einen Stream gibt, sobald sie verfügbar ist. Es ist eine vereinfachte Lösung, aber es ist ein guter Ausgangspunkt. Es ist fadensicher.

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

Sie können diesen Stream wie gewohnt verwenden:

using (var stream = await FileStreamGetter.GetStreamAsync())
{
    Console.WriteLine(stream.Length);
}
4
Ivan Branets
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");

Hoffe das hilft!

4
Julian

die einzige Möglichkeit, die ich kenne, ist die Verwendung der exklusiven Win32-Sperren-API, die nicht zu schnell ist, aber es gibt Beispiele.

Die meisten Leute, um eine einfache Lösung dafür zu finden, versuchen Sie einfach, Loops zu probieren.

4
Luke Schafer

Sie können meine Bibliothek für den Zugriff auf Dateien aus mehreren Apps verwenden.

Sie können es von Nuget aus installieren: Install-Package Xabe.FileLock

Wenn Sie weitere Informationen dazu wünschen, überprüfen Sie die folgenden Informationen: __. https://github.com/tomaszzmuda/Xabe.FileLock

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

die fileLock.Acquire-Methode gibt nur dann "true" zurück, wenn die Datei für dieses Objekt exklusiv gesperrt werden kann. Die App, die die hochzuladende Datei muss, muss dies jedoch auch in der Dateisperre tun.

2
Tomasz Żmuda

Bei den oben genannten akzeptierten Antworten tritt ein Problem auf, bei dem der Code nicht funktioniert, wenn die Datei zum Schreiben mit einem FileShare.Read-Modus geöffnet wurde oder wenn die Datei über ein Schreibschutzattribut verfügt. Diese modifizierte Lösung arbeitet am zuverlässigsten, wobei zwei Dinge zu beachten sind (wie auch für die akzeptierte Lösung):

  1. Es funktioniert nicht für Dateien, die mit einem Schreibfreigabemodus geöffnet wurden
  2. Threading-Probleme werden dabei nicht berücksichtigt, daher müssen Sie sie sperren oder Threading-Probleme separat behandeln.

Unter Berücksichtigung der obigen Ausführungen wird geprüft, ob die Datei entweder locked ist, um zu schreiben, oder locked, um das Lesen von zu verhindern:

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

Hier ist ein Code, der soweit ich am besten sagen kann, dasselbe tut wie die akzeptierte Antwort, jedoch mit weniger Code:

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

Ich denke jedoch, dass es robuster ist, dies auf folgende Weise zu tun:

    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
cdiggins

Nach meiner Erfahrung möchten Sie dies normalerweise tun, dann "schützen" Sie Ihre Dateien, um etwas Besonderes zu tun, und verwenden Sie die "geschützten" Dateien. Wenn Sie nur eine Datei wie diese verwenden möchten, können Sie den Trick verwenden, der in der Antwort von Jeremy Thompson erläutert wird. Wenn Sie jedoch bei vielen Dateien versuchen, dies zu tun (zum Beispiel, wenn Sie ein Installationsprogramm schreiben), haben Sie eine Menge Schmerzen.

Ein sehr eleganter Weg, dies zu lösen, besteht darin, dass Sie im Dateisystem keinen Ordnernamen ändern können, wenn eine der Dateien dort verwendet wird. Behalten Sie den Ordner im selben Dateisystem und es funktioniert wie ein Zauber.

Beachten Sie, dass Sie sich der offensichtlichen Möglichkeiten bewusst sein sollten, wie dies genutzt werden kann. Schließlich werden die Dateien nicht gesperrt. Beachten Sie auch, dass es andere Gründe gibt, die dazu führen können, dass Ihre Move-Operation fehlschlägt. Offensichtlich kann die richtige Fehlerbehandlung (MSDN) hier helfen.

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

Für einzelne Dateien würde ich bei dem Sperrvorschlag von Jeremy Thompson bleiben.

1
atlaste

Abgesehen von der Arbeit mit 3-Liner und nur als Referenz: Wenn Sie die full-blown-Informationen wünschen, gibt es ein kleines Projekt in Microsoft Dev Center:

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

Aus der Einleitung:

Der in .NET Framework 4.0 entwickelte C # -Beispielcode würde in .__ helfen. Herausfinden, welches der Prozess ist, für den eine Datei gesperrt ist . Die in rstrtmgr.dll enthaltene RmStartSession - Funktion wurde mit .__ ausgeführt. wird zum Erstellen einer Neustartmanagersitzung und entsprechend der Rückgabe verwendet. Dadurch wird eine neue Instanz des Win32Exception-Objekts erstellt. Nach dem Registrieren der Ressourcen in einer Restart Manager-Sitzung über RmRegisterRescources -Funktion, RmGetList -Funktion wird zur Überprüfung von .__ aufgerufen. Welche Anwendungen verwenden eine bestimmte Datei durch Aufzählen von das Array RM_PROCESS_INFO.

Es funktioniert durch Verbinden mit der "Restart Manager Session".

Der Neustart-Manager verwendet die Liste der in der Sitzung registrierten Ressourcen für Ermitteln, welche Anwendungen und Dienste heruntergefahren und neu gestartet werden müssen . Ressourcen können anhand von Dateinamen, Dienstkurznamen oder .__ identifiziert werden. RM_UNIQUE_PROCESS-Strukturen, die laufende Anwendungen beschreiben.

Es könnte ein wenig überarbeitet für Ihre speziellen Bedürfnisse sein ... Aber wenn es das ist, was you Sie wollen, dann machen Sie das vs-Projekt.

1
Bernhard

Ich bin gespannt, ob dies WTF-Reflexe auslöst. Ich habe einen Prozess, der ein PDF Dokument aus einer Konsolen-App erstellt und anschließend startet. Ich befasste mich jedoch mit einer Schwachstelle, bei der, wenn der Benutzer den Prozess mehrmals ausführte und dieselbe Datei erzeugte, ohne zuvor die zuvor erzeugte Datei zu schließen, die App eine Ausnahme auslöste und sterben würde. Dies war ein ziemlich häufiges Ereignis, da Dateinamen auf Angebotsnummern basieren.

Anstatt auf so unnachahmliche Art und Weise zu versagen, entschied ich mich, auf automatisch inkrementierte Dateiversionierung zu setzen:

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

Wahrscheinlich kann dem catch-Block etwas mehr Aufmerksamkeit geschenkt werden, um sicherzustellen, dass ich die korrekten IOException (s) erwische. Ich werde wahrscheinlich auch den App-Speicher beim Start löschen, da diese Dateien sowieso temporär sein sollen.

Ich weiß, dass dies über die Frage des OP hinausgeht, einfach zu prüfen, ob die Datei verwendet wird, aber dies war in der Tat das Problem, das ich lösen wollte, als ich hier ankam. Vielleicht ist es für jemanden nützlich.

0
Vinney Kelly

Würde so etwas helfen?

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

    }
}
0
Tadej

Ich musste einmal PDFs in ein Online-Backup-Archiv hochladen. Die Sicherung würde jedoch fehlschlagen, wenn der Benutzer die Datei in einem anderen Programm (z. B. PDF reader) geöffnet hätte. In meiner Hast habe ich versucht, einige der besten Antworten in diesem Thread zu finden, konnte sie aber nicht zum Laufen bringen. Was für mich funktioniert hat, war zu versuchen, die PDF -Datei in sein eigenes Verzeichnis zu verschieben. Ich stellte fest, dass dies fehlschlagen würde, wenn die Datei in einem anderen Programm geöffnet wäre und wenn das Verschieben erfolgreich wäre, wäre kein Wiederherstellungsvorgang erforderlich, wie dies der Fall wäre, wenn sie in ein separates Verzeichnis verschoben würde. Ich möchte meine grundlegende Lösung veröffentlichen, falls sie für bestimmte Anwendungsfälle anderer hilfreich sein kann.

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
}