Frage

Ich bin ein Programm in C # zu schreiben, die wiederholt Datei zugreifen 1 Bild muss. Die meiste Zeit es funktioniert, aber wenn mein Computer schnell ausgeführt wird, wird es versuchen, auf die Datei zuzugreifen, bevor es wieder auf das Dateisystem gespeichert ist und einen Fehler aus: „Datei in Verwendung von einem anderen Prozess“ .

Ich möchte einen Weg, um dieses zu finden, aber alle meine googeln hat ergab nur Kontrollen unter Verwendung von Ausnahmebehandlung zu schaffen. Das ist gegen meine Religion, so dass ich mich gefragt, ob jemand einen besseren Weg, es zu tun hat?

War es hilfreich?

Lösung

aktualisierte Hinweis auf dieser Lösung : mit FileAccess.ReadWrite Überprüfung wird für Read-Only-Dateien fehlschlagen, so dass die Lösung mit FileAccess.Read zu überprüfen, wurde geändert. Während diese Lösung, weil versucht, mit FileAccess.Read arbeitet zu überprüfen schlägt fehl, wenn die Datei eine Schreib- oder Lesesperre dafür hat, jedoch wird diese Lösung nicht funktionieren, wenn die Datei nicht über eine Schreib- oder Lesesperre dafür, dh es wurde geöffnet (zum Lesen oder Schreiben) mit FileShare.Read oder FileShare.Write Zugriff.

ORIGINAL: Ich habe diesen Code in den letzten paar Jahren, und ich habe keine Probleme mit ihm.

Verstehen Sie Ihre Bedenken Ausnahmen zu verwenden, aber Sie können sie die ganze Zeit nicht 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;
}

Andere Tipps

Sie können von einem Thread Race-Bedingung auf diese leiden, die es Beispiele dafür verwendet wird, als eine Sicherheitslücke dokumentiert sind. Wenn Sie überprüfen, dass die Datei vorhanden ist, aber dann versuchen, es zu nutzen, an diesem Punkt werfen konnte, die ein böswilliger Benutzer zu zwingen und zu nutzen, in Ihrem Code verwenden können.

Am besten ist es ein Versuch catch / finally, die die Datei-Handle zu erhalten versucht.

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

Verwenden Sie diese Funktion 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 Performance-Gründen empfehle ich Ihnen, den Inhalt der Datei in dem gleichen Betrieb 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;
}

Versuchen Sie es selbst:

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

Vielleicht könnten Sie einen Filesystemwatcher und Uhr für das Changed-Ereignis.

Ich habe das selbst nicht verwendet, aber es könnte einen Versuch wert sein. Wenn die Filesystemwatcher für diesen Fall ein bisschen schwer erweisen, würde ich für die try / catch / Schlafschleife gehen.

Nur die Ausnahme verwenden, wie beabsichtigt. Akzeptieren Sie, dass die Datei in Gebrauch ist, und versuchen Sie es erneut, so oft, bis die Aktion abgeschlossen ist. Dies ist auch die effizienteste, weil Sie keine Zyklen verschwenden den Zustand überprüft, bevor Sie handeln.

Mit der Funktion unten, zum Beispiel

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

Wiederverwendbare Methode, die mal aus nach 2 Sekunden

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

der einzige Weg, ich kenne ist das Win32-exklusive Sperre API zu verwenden, die nicht zu schnell ist, aber Beispiele existieren.

Die meisten Menschen, für eine einfache Lösung für dieses Problem, einfach try / catch / Schlaf-Loops.

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

Hope, das hilft!

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

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 Strom wie gewohnt verwenden:

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

Die akzeptierten Antworten leiden über ein Problem, wenn die Datei mit einem FileShare.Read Modus geöffnet wurde, zum Schreiben oder wenn die Datei eines Read-Only hat die Code-Attribut wird nicht funktionieren. Diese modifizierte Lösung funktioniert am sichersten, mit zwei Dingen im Auge (als wahr für die akzeptierte Lösung auch) zu halten:

  1. Es wird nicht für Dateien arbeiten, die mit einem Schreibfreigabemodus
  2. geöffnet wurde
  3. Dies gilt nicht berücksichtigt Threadingprobleme, damit Sie es brauchen zu sperren oder Threadingprobleme Griff getrennt.

Halten Sie die oben im Kopf, diese überprüft, ob die Datei entweder gesperrt für das Schreiben oder gesperrt zu verhindern Lese :

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

Neben der Arbeit 3-Liner und nur als Referenz: Wenn Sie die ausgewachsenen Informationen wollen - gibt es ein kleines Projekt auf Microsoft Dev Center:

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

Aus der Einleitung:

  

Das C # Codebeispiel entwickelt in .NET Framework 4.0 würde helfen   herauszufinden, welche der Prozess ist, der auf eine Datei eine Sperre aufweist.    RmStartSession Funktion, die in rstrtmgr.dll enthalten ist, wurde   verwendet, um einen Neustart des Manager-Sitzung zu erstellen und nach der Rückkehr   führt eine neue Instanz von Win32Exception Objekt erstellt. Nach   Registrierung der Ressourcen zu einer Restart Manager-Sitzung über    RmRegisterRescources Funktion RmGetList Funktion wird aufgerufen, um zu überprüfen   Was sind die Anwendungen mit einer bestimmten Datei durch Auflisten    RM_PROCESS_INFO Array.

Es funktioniert, indem auf die "Restart Manager Session" zu verbinden.

  

Der Restart Manager verwendet die Liste der Ressourcen mit der Sitzung registriert   bestimmen, welche Anwendungen und Dienste müssen heruntergefahren und neu gestartet werden.    Ressourcen können durch Dateinamen, Service Kurznamen identifiziert werden, oder   RM_UNIQUE_PROCESS Strukturen , die laufenden Anwendungen zu beschreiben.

Es könnte ein wenig sein overengineered für Ihre speziellen Bedürfnisse ... Aber wenn es das ist, was Sie wollen, gehen Sie voran und ergreifen Sie die vs-Projekt.

Hier ist ein Code, die so weit wie ich kann am besten tell tut das Gleiche wie die akzeptierte Antwort, aber mit weniger Code:

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

Aber ich denke, es robuster ist es in der folgenden Art und 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);
    }

Sie können meine Bibliothek verwenden für Dateien von mehreren Anwendungen zugreifen.

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

Wenn Sie weitere Informationen wünschen über sie überprüfen https://github.com/tomaszzmuda/Xabe.FileLock

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

fileLock.Acquire Methode true zurück, nur wenn die Datei exklusiv für dieses Objekt sperrt. Aber App-Datei, die das Hochladen muss es auch in Dateisperre tun. Wenn das Objekt nicht zugegriffen metod gibt false zurück.

Nach meiner Erfahrung möchten Sie in der Regel, dies zu tun, dann ‚schützen‘ Ihre Dateien etwas Phantasie und verwenden Sie dann die ‚geschützten‘ Dateien zu tun. Wenn Sie nur eine Datei haben Sie so verwenden möchten, können Sie den Trick verwenden, die in der Antwort von Jeremy Thompson erklärt wird. Wenn Sie jedoch versuchen, diese Dateien auf viel zu tun (sagen wir, zum Beispiel, wenn Sie ein Installationsprogramm gerade schreiben), sind Sie für ziemlich viel Schmerz.

Eine sehr elegante Art und Weise dieses Problem gelöst werden kann, ist unter Ausnutzung der Tatsache, dass Ihr Dateisystem wird nicht zulassen, einen Ordnernamen zu ändern, wenn eine der Dateien dort wird es verwendet wird. Halten Sie den Ordner, in dem gleichen Dateisystem und es wird wie ein Zauber funktionieren.

Sie zur Kenntnis, dass Sie Kenntnis von den offensichtlichen Möglichkeiten sollten diese genutzt werden können. Schließlich werden die Dateien nicht gesperrt werden. Beachten Sie auch, dass es andere Gründe, die in Ihrem Move Betrieb führen zum Scheitern verurteilt. Offensichtlich die richtige Fehlerbehandlung (MSDN) kann 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, die ich mit dem Verriegelungs Vorschlag von Jeremy Thompson geschrieben haften bleiben würde.

Ich bin gespannt, ob dies irgendwelche WTF Reflexe auslöst. Ich habe einen Prozess, der erstellt und anschließend startet ein PDF-Dokument aus einer Konsolenanwendung. Allerdings war ich mit einem Gebrechlichkeit handelt, wo, wenn der Benutzer den Vorgang mehrmals ausgeführt wurde, die gleiche Datei zu erzeugen, ohne zuerst die zuvor erzeugte Datei geschlossen wird, die App eine Ausnahme auslösen würde und sterben. Das war ein ziemlich häufiges Vorkommen, weil Dateinamen basiert auf Verkaufsquote Zahlen.

Anstatt in einer solchen ungraceful Weise versagt, entschied ich mich auf Auto erhöht Dateiversionierung verlassen:

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 etwas mehr Sorgfalt auf den catch Block gegeben werden, um sicherzustellen, dass ich die richtige IOException fange (s). Ich werde wahrscheinlich räumen beim Start auch die App-Speicher da diese Dateien gedacht sind sowieso nur vorübergehend sein.

Ich weiß, das über den Rahmen der Frage des OP geht einfach zu überprüfen, ob die Datei in Gebrauch ist, aber das war in der Tat das Problem, das ich war auf der Suche zu lösen, als ich hier angekommen, so wird es vielleicht jemand anderen nützlich sein.

wäre so etwas wie diese Hilfe?

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

    }
}

ich einmal benötigt PDFs zu einem Online-Backup-Archiv zu laden. Aber die Sicherung fehlschlagen würde, wenn der Benutzer die Datei in einem anderen Programm geöffnet hat (wie zB PDF-Reader). In der Eile habe ich versucht, ein paar der Top-Antworten in diesem Thread konnte sie aber nicht bekommen zu arbeiten. Was hat Arbeit für mich versuchte, die PDF-Datei ein eigenes Verzeichnis zu bewegen. Ich fand, dass dies fehlschlagen, wenn die Datei in einem anderen Programm geöffnet ist, und wenn der Zug dort erfolgreich wäre, würde keine restore-Operation sein erforderlich, da es wäre, wenn es in ein separaten Verzeichnis verschoben wurde. Ich mag meine grundlegende Lösung für den Fall schreiben kann es für andere spezifische Anwendungsfälle nützlich sein.

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
}

Versuchen Sie, und verschieben / kopieren Sie die Datei in ein temporäres Verzeichnis. Wenn Sie können, hat es keine Sperre, und Sie können, ohne sich Sperren in der temp dir sicher arbeiten. Else nur versuchen, es wieder in x Sekunden zu bewegen.

Ich benutze dieses Problem zu umgehen, aber ich habe eine Zeitspanne zwischen wenn ich überprüfen Sie die Datei mit IsFileLocked Verriegelungsfunktion und wenn ich die Datei öffnen. In diesem Zeitraum einige andere Thread die Datei öffnen kann, so werde ich IOException erhalten.

Also, ich hinzugefügt zusätzlichen Code für diese. In meinem Fall möchte ich Last 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!!!!!");
            }
        }

Was denken Sie? Kann ich einige etwas ändern? Vielleicht habe ich nicht IsFileBeingUsed Funktion überhaupt verwenden?

Danke

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top