Czy istnieje sposób na sprawdzenie, czy plik jest używany?

Piszę program w C#, który wymaga wielokrotnego dostępu do 1 Pliku obrazu. Przez większość czasu działa, ale jeśli mój komputer działa szybko, spróbuje uzyskać dostęp do pliku przed zapisaniem go z powrotem do systemu plików i wyświetli błąd: "Plik używany przez inny proces" .

Chciałbym znaleźć sposób na obejście tego, ale całe moje Googlowanie przyniosło tylko tworzenie sprawdzeń za pomocą obsługi wyjątków. To wbrew mojej religii, więc zastanawiałem się, czy ktoś ma lepszy sposób na robiąc to?

Author: Jeremy Thompson, 2009-05-18

16 answers

Zaktualizowana uwaga na temat tego rozwiązania : sprawdzanie za pomocą {[1] } nie powiedzie się dla plików Tylko do odczytu, więc rozwiązanie zostało zmodyfikowane do sprawdzania za pomocą FileAccess.Read. Chociaż to rozwiązanie działa, ponieważ próba sprawdzenia za pomocą FileAccess.Read zakończy się niepowodzeniem, Jeśli plik ma blokadę zapisu lub odczytu, to jednak rozwiązanie to nie zadziała, jeśli plik nie ma blokady zapisu lub odczytu, tzn. został otwarty (do odczytu lub zapisu) za pomocą FileShare.Przeczytaj lub FileShare.Dostęp do zapisu.

Oryginalny: Używałem ten kod od kilku lat i nie miałem z nim żadnych problemów.

Zrozum swoje wahanie dotyczące używania wyjątków, ale nie możesz ich unikać cały czas:

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;
}
 457
Author: ChrisW,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-16 17:41:24

Możesz cierpieć z powodu stanu wyścigu wątku, który jest udokumentowany jako luka w zabezpieczeniach. Jeśli sprawdzisz, czy plik jest dostępny, ale potem spróbuj go użyć, możesz rzucić w tym momencie, co złośliwy użytkownik może wykorzystać do wymuszenia i wykorzystania w kodzie.

Najlepiej jest spróbować złapać / wreszcie, który próbuje uzyskać uchwyt pliku.

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.
}
 518
Author: Spence,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-12 16:58:55

Użyj tego, aby sprawdzić, czy plik jest zablokowany:

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

Ze względów wydajnościowych zalecam przeczytanie zawartości pliku w tej samej operacji. Oto kilka przykładów:

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

Wypróbuj sam:

byte[] output1 = Helper.ReadFileBytes(@"c:\temp\test.txt");
string output2 = Helper.ReadFileTextWithEncoding(@"c:\temp\test.txt");
string output3 = Helper.ReadFileTextNoEncoding(@"c:\temp\test.txt");
 81
Author: Jeremy Thompson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-20 07:06:55

Być może mógłbyś użyć FileSystemWatcher i obserwować zmienione Zdarzenie.

Sam tego nie używałem, ale może warto spróbować. Jeśli filesystemwatcher okaże się trochę ciężki dla tego przypadku, chciałbym przejść do try/catch/sleep loop.

 6
Author: Karl Johan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-18 06:52:09

Jedyny sposób, jaki znam, to użycie Win32 exclusive lock API, które nie jest zbyt szybkie, ale przykłady istnieją.

Większość ludzi, dla prostego rozwiązania tego, po prostu spróbować/złapać / sleep loops.

 4
Author: Luke Schafer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-18 06:42:31
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");
Mam nadzieję, że to pomoże!
 4
Author: Julian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-22 17:30:16

Po prostu użyj wyjątku zgodnie z przeznaczeniem. Zaakceptuj, że plik jest w użyciu i spróbuj ponownie, wielokrotnie, dopóki akcja nie zostanie zakończona. Jest to również najbardziej efektywne, ponieważ nie marnujesz żadnych cykli sprawdzając stan przed działaniem.

Użyj poniższej funkcji, na przykład

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

Metoda Wielokrotnego Użytku, która kończy się po 2 sekundach

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);
}
 4
Author: kernowcode,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-26 15:29:09

Możesz zwrócić zadanie, które da ci strumień, gdy tylko będzie dostępny. To uproszczone rozwiązanie, ale dobry punkt wyjścia. Jest Bezpieczna.

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

Możesz używać tego strumienia jak zwykle:

using (var stream = await FileStreamGetter.GetStreamAsync())
{
    Console.WriteLine(stream.Length);
}
 4
Author: Ivan Branets,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-20 12:33:01

Zaakceptowane odpowiedzi powyżej cierpią problem, gdy plik został otwarty do zapisu z FileShare.Tryb odczytu lub jeśli plik ma atrybut Tylko do odczytu, kod nie będzie działał. To zmodyfikowane rozwiązanie działa najbardziej niezawodnie, mając na uwadze dwie rzeczy (tak samo jak w przypadku przyjętego rozwiązania):

  1. nie będzie działać dla plików, które zostały otwarte w trybie zapisu share
  2. nie uwzględnia to problemów z gwintowaniem, więc musisz go zablokować lub obsłużyć problemy z gwintowaniem osobno.

Mając powyższe na uwadze, sprawdza, czy plik jest zablokowany do zapisu lub zablokowany, aby zapobiec odczytaniu :

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
Author: rboy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-15 14:31:30

Oto jakiś kod, który z tego co wiem najlepiej robi to samo co akceptowana odpowiedź, ale z mniejszym kodem:

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

Jednak myślę, że jest to bardziej wytrzymałe, aby zrobić to w następujący sposób:

    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
Author: cdiggins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-22 20:46:36

Możesz użyć mojej biblioteki do uzyskiwania dostępu do plików z wielu aplikacji.

Można go zainstalować z NuGet: Install-Package Xabe.FileLock

Jeśli chcesz uzyskać więcej informacji na ten temat sprawdź https://github.com/tomaszzmuda/Xabe.FileLock

ILock fileLock = new FileLock(file);
if(fileLock.Acquire(TimeSpan.FromSeconds(15), true))
{
    using(fileLock)
    {
        // file operations here
    }
}
FileLock.Metoda Acquire zwróci true tylko jeżeli może zablokować plik wyłącznie dla tego obiektu. Ale aplikacja, która przesyła plik, musi to zrobić również w blokadzie plików. Jeżeli obiekt jest niedostępny, metoda zwraca false.
 2
Author: Tomasz Żmuda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-07 10:49:34

Z mojego doświadczenia wynika, że zazwyczaj chcesz to zrobić, a następnie "chronić" swoje pliki, aby zrobić coś wymyślnego, a następnie użyć "chronionych" plików. Jeśli masz tylko jeden plik, którego chcesz użyć w ten sposób, możesz użyć sztuczki wyjaśnionej w odpowiedzi Jeremy ' ego Thompsona. Jeśli jednak spróbujesz to zrobić na wielu plikach (np. pisząc instalator), będziesz musiał ponieść niemałą szkodę.

Bardzo eleganckim sposobem na rozwiązanie tego problemu jest użycie faktu, że Twój system plików nie pozwoli Ci zmienić nazwy folderu, jeśli jeden z plików jest używany. Zachowaj folder w tym samym systemie plików i będzie działał jak urok.

Należy pamiętać, że należy zdawać sobie sprawę z oczywistych sposobów, w jaki można to wykorzystać. W końcu pliki nie zostaną zablokowane. Należy również pamiętać, że istnieją inne powody, które mogą spowodować niepowodzenie twojej operacji Move. Oczywiście poprawna obsługa błędów (MSDN) może tutaj pomóc.

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

Dla poszczególnych plików trzymałbym się blokady sugestia wysłana przez Jeremy Thompson.

 1
Author: atlaste,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-21 11:10:11

Oprócz pracy 3-linerów i tylko dla Odniesienia: jeśli chcesz full blown informacji - jest mały projekt na Microsoft Dev Center:

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

Ze wstępu:

Przykładowy kod C# opracowany w. NET Framework 4.0 pomoże w ustalenie, który proces ma blokadę pliku. rmstartsession funkcja, która jest zaliczony do rstrtmgr.dll został służy do utworzenia sesji restart managera i zgodnie z powrotem wynik zostanie utworzona nowa instancja obiektu Win32Exception. Po Rejestracja zasobów do sesji Restart Managera poprzez rmregisterrescources function, rmgetlist funkcja jest wywoływana do sprawdzenia jakie aplikacje używają konkretnego pliku, wyliczając tablica rm_process_info .

Działa poprzez podłączenie do " Restart Sesja Menedżerska".

Menedżer restartu wykorzystuje listę zasobów zarejestrowanych w sesji do określ, które aplikacje i usługi muszą zostać wyłączone i ponownie uruchomione. zasoby mogą być identyfikowane za pomocą nazw plików, krótkich nazw usług lub Rm_unique_process struktury opisujące uruchomione aplikacje.

To może być trochę overengineer dla Twoich szczególnych potrzeb... Ale jeśli to jest to, co chcesz, śmiało i chwyć vs-project.

 1
Author: Bernhard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-09 06:56:20

Ciekawi mnie, czy to wywołuje odruchy WTF. Mam proces, który tworzy, a następnie uruchamia dokument PDF z aplikacji konsoli. Jednak miałem do czynienia z słabością, w której jeśli użytkownik miałby uruchomić proces wiele razy, generując ten sam plik bez uprzedniego zamknięcia wcześniej wygenerowanego pliku, aplikacja rzuciłaby wyjątek i umrze. Było to dość częste zjawisko, ponieważ nazwy plików są oparte na numerach cytatów sprzedaży.

Zamiast zawodzić w tak niewdzięczny sposób, postanowiłem polegać na auto-incremented wersjonowanie plików:

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

Prawdopodobnie można zadbać o Blok catch, aby upewnić się, że wyłapuję poprawne IOException(s). Prawdopodobnie wyczyszczę również pamięć aplikacji podczas uruchamiania, ponieważ te pliki mają być tymczasowe.

Zdaję sobie sprawę, że wykracza to poza zakres pytania OP, polegającego na sprawdzeniu, czy plik jest w użyciu, ale to był rzeczywiście problem, który chciałem rozwiązać, gdy przyjechałem tutaj więc być może będzie to przydatne dla kogoś innego.

 0
Author: Vinney Kelly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-05 12:07:23

Spróbuj przenieść / skopiować plik do katalogu temp. Jeśli możesz, nie ma blokady i możesz bezpiecznie pracować w temp dir bez uzyskiwania zamków. W przeciwnym razie po prostu spróbuj przenieść go ponownie w x sekund.

 -2
Author: Carra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-18 09:11:04

Używam tego obejścia, ale mam czas między sprawdzeniem blokady pliku za pomocą funkcji IsFileLocked A otwarciem pliku. W tym czasie inny wątek może otworzyć plik, więc otrzymam IOException.

Dodałem do tego dodatkowy kod. W moim przypadku chcę załadować 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!!!!!");
            }
        }
Co o tym myślisz? Mogę coś zmienić? Może w ogóle nie musiałem używać funkcji IsFileBeingUsed?

Thanks

 -2
Author: zzfima,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-17 15:45:31