Czy istnieje sposób sprawdzenia, czy plik jest w użyciu?

845

Piszę program w języku C #, który musi wielokrotnie uzyskiwać dostęp 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, zanim zostanie zapisany z powrotem w systemie plików, i zgłosi błąd: „Plik używany przez inny proces” .

Chciałbym znaleźć sposób na obejście tego, ale cały mój Googling dał tylko tworzenie czeków przy użyciu obsługi wyjątków. To jest sprzeczne z moją religią, więc zastanawiałem się, czy ktoś ma lepszy sposób na zrobienie tego?

Dawsy
źródło
30
W porządku, możesz to przetestować, sprawdzając wszystkie otwarte uchwyty w systemie. Ponieważ jednak system Windows jest wielozadaniowym systemem operacyjnym, istnieje szansa, że ​​zaraz po uruchomieniu kodu w celu ustalenia, czy plik jest otwarty, a Ty uważasz, że tak nie jest, kod procesowy zacznie go używać, zanim spróbujesz użyj go, pojawi się błąd. Ale sprawdzanie najpierw nie jest niczym złym; tylko nie zakładaj, że nie jest używany, gdy go potrzebujesz.
BobbyShaftoe
3
Ale tylko dla tego konkretnego problemu; Polecam nie sprawdzać uchwytów plików i po prostu wypróbować określoną liczbę razy, powiedzmy 3-5, zanim się nie powiedzie.
BobbyShaftoe
Jak generowany jest ten plik obrazu? Czy możesz zatrzymać / spać / wstrzymać program, dopóki generowanie nie zostanie zakończone? To zdecydowanie lepszy sposób na poradzenie sobie z sytuacją. Jeśli nie, to nie sądzę, że można uniknąć obsługi wyjątków.
Catchwa
Czy całe stosowanie wyjątków nie jest sprawdzeniem pewnych założeń poprzez zrobienie czegoś potencjalnie niebezpiecznego, a jednocześnie celowo nie wyklucza możliwości niepowodzenia?
jwg
26
Wasza filozofia źle rozumie wyjątki. Większość ludzi uważa, że ​​wyjątki oznaczają święte bzdury, zgubę, zgubę, śmierć, śmierć. Kiedy wyjątek oznacza .... wyjątek. Oznacza to, że wydarzyło się coś wyjątkowego, co trzeba „obsłużyć” (lub uwzględnić). Może chcesz spróbować ponownie uzyskać dostęp do danych, może użytkownik musi wiedzieć, że nie możesz uzyskać połączenia. Co robisz? Obsługujesz wyjątek ConnectionFailedException i powiadamiasz użytkownika, więc być może przestanie próbować po godzinie i zauważy, że kabel jest odłączony.
Lee Louviere,

Odpowiedzi:

543

Zaktualizowana UWAGA dotycząca tego rozwiązania : Sprawdzanie za pomocą FileAccess.ReadWritenie powiedzie się w przypadku plików tylko do odczytu, więc rozwiązanie zostało zmodyfikowane w celu sprawdzania za pomocą FileAccess.Read. Chociaż to rozwiązanie działa, ponieważ próba sprawdzenia za pomocą FileAccess.Readnie powiedzie się, jeśli plik ma blokadę zapisu lub odczytu, jednak to rozwiązanie nie będzie działać, jeśli plik nie ma blokady zapisu lub odczytu, tj. Został otwarty (do odczytu lub zapisu) za pomocą dostępu FileShare.Read lub FileShare.Write.

ORYGINALNY: Używam tego kodu od kilku lat i nie miałem z nim żadnych problemów.

Poznaj swoje wahanie dotyczące korzystania z wyjątków, ale nie możesz ich cały czas unikać:

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;
}
ChrisW
źródło
60
To świetne rozwiązanie, ale mam jeden komentarz - możesz nie otwierać pliku w trybie dostępu FileAccess. Przeczytaj, ponieważ ReadWrite zawsze zawiedzie, jeśli plik będzie tylko do odczytu.
adeel825
219
-1. To kiepska odpowiedź, ponieważ plik może zostać zablokowany przez inny wątek / proces po tym, jak zostanie zamknięty w IsFileLocked i zanim Twój wątek będzie miał szansę go otworzyć.
Polyfun
16
Myślę, że to świetna odpowiedź. Używam tego jako metody rozszerzenia á la public static bool IsLocked(this FileInfo file) {/*...*/}.
Manuzor
53
@ChrisW: możesz się zastanawiać, co się dzieje. Nie bądź zaniepokojony. Poddajesz się
Pierre Lebeaupin
16
@ChrisW Dlaczego to zła rzecz. Ta społeczność jest tutaj, aby wskazać dobre i złe odpowiedzi. Jeśli grupa profesjonalistów zauważy, że jest to zła rzecz, i przyłącz się do głosowania, oznacza to, że witryna jest WAI. I zanim uzyskasz negatywne zdanie, jeśli przeczytasz ten artykuł, powiedzą, aby „głosować za właściwą odpowiedzią”, a nie głosować za złą. Czy chcesz, aby wyjaśniali również swoje opinie w komentarzach. Dziękujemy za zapoznanie mnie z inną dobrą stroną!
Lee Louviere,
569

Możesz cierpieć na warunek wyścigu wątków, który jest udokumentowany, przykłady wykorzystania tego jako luki w zabezpieczeniach. Jeśli sprawdzisz, czy plik jest dostępny, ale następnie spróbujesz go użyć, możesz w tym momencie rzucić, którego złośliwy użytkownik może użyć do wymuszenia i wykorzystania w kodzie.

Twój najlepszy zakład to try catch / 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.
}
Spence
źródło
124
+1. Nie ma w 100% bezpiecznego sposobu, aby „dowiedzieć się, czy plik jest w użyciu”, ponieważ milisekundy po sprawdzeniu mogą nie być już używane lub odwrotnie. Zamiast tego wystarczy otworzyć plik i użyć go, jeśli nie ma wyjątków.
Sedat Kapanoglu
8
Szkoda .NET nie obsługuje CAS. Coś w stylu TryOpenFile (Ref FileHandle), który zwraca sukces / porażkę. Zawsze powinno być obejście problemu, a nie poleganie wyłącznie na obsłudze wyjątków. Zastanawiam się, jak to robi Microsoft Office.
TamusJRoyce
2
Kluczową rzeczą do zrozumienia tutaj jest to, że ten interfejs API po prostu używa interfejsu API systemu Windows, aby uzyskać uchwyt pliku. W związku z tym muszą przetłumaczyć kod błędu otrzymany z interfejsu API języka C i zawinąć go w wyjątek do zgłoszenia. Mamy obsługę wyjątków w .Net, więc dlaczego z niej nie skorzystać. W ten sposób możesz napisać czystą ścieżkę do przodu w kodzie i pozostawić obsługę błędów w osobnej ścieżce kodu.
Spence
36
Instrukcja using ma zapewnić, że strumień zostanie zamknięty po zakończeniu. Myślę, że przekonasz się, że using () {} zawiera mniej znaków niż try {} wreszcie {obj.Dispose ()}. Przekonasz się również, że teraz musisz zadeklarować odwołanie do obiektu poza instrukcją using, co jest bardziej typowe. Jeśli masz wyraźny interfejs, musisz także przesyłać. Wreszcie chcesz pozbyć się jak najszybciej, a logika wreszcie może mieć interfejs użytkownika lub dowolne inne długo działające działania, które nie mają wiele wspólnego z wywoływaniem IDispose. </rant>
Spence
2
to nie neguje faktu, że musisz zadeklarować swój obiekt poza próbą i musisz jawnie wywołać dispose, co użycie robi dla ciebie i oznacza to samo.
Spence
92

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ędu na wydajność zalecam przeczytanie zawartości pliku podczas 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");
Jeremy Thompson
źródło
8
Głosowałbym za tym, gdyby nie było tam tak wielu „magicznych liczb” en.wikipedia.org/wiki/Magic_number_(programming)
Kris
3
Miałem na myśli porównania błędów CodeCode, a nie przesunięcia bitów. choć teraz o tym wspominasz ...
Kris
1
Twój Catch powinien być włączony IOException, zamiast ogólnie, Exceptiona następnie test typu.
Askolein
3
@JeremyThompson niestety umieściłeś ten konkretny IOExceptionpo ogólnym. Ogólny złapie wszystko, co przechodzi, a konkretny IOExceptionzawsze będzie samotny. Po prostu zamień dwa.
Askolein
Podoba mi się to rozwiązanie. Jeszcze jedna sugestia: wewnątrz haczyka jako jeszcze raz do if (IsFileLocked (ex)) rzuciłbym ex. W ten sposób obsłużymy przypadek, w którym plik nie istnieje (lub inny wyjątek IOException), zgłaszając wyjątek.
shindigo
7

Po prostu użyj wyjątku zgodnie z przeznaczeniem. Zaakceptuj, że plik jest w użyciu i spróbuj ponownie, aż do zakończenia działania. Jest to również najbardziej wydajne, ponieważ nie marnujesz żadnych cykli sprawdzania stanu przed działaniem.

Użyj na przykład poniższej funkcji

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);
}
kernowcode
źródło
6

Być może możesz użyć FileSystemWatcher i obserwować wydarzenie Zmienione.

Nie użyłem tego sam, ale może warto spróbować. Jeśli obserwator systemu plików okaże się nieco ciężki w tym przypadku, wybrałbym pętlę try / catch / sleep.

Karl Johan
źródło
1
Korzystanie z FileSystemWatcher nie pomaga, ponieważ zdarzenia Utworzone i Zmienione powstają na początku tworzenia / zmiany pliku. Nawet małe pliki wymagają więcej czasu na napisanie i zamknięcie przez system operacyjny niż aplikacja .NET musi być uruchomiona przez wywołanie zwrotne FileSystemEventHandler. To takie smutne, ale nie ma innej opcji niż oszacowanie czasu oczekiwania przed uzyskaniem dostępu do pliku lub
FileSystemWatcher nie radzi sobie jednak z wieloma zmianami jednocześnie, więc bądź ostrożny.
Ben F.
1
BTW, zauważyliście podczas debugowania i oglądania wątków, które MS nazywa swoim własnym FSW „FileSystemWather”? Co to w ogóle jest?
devlord
Właśnie dostałem ten problem, ponieważ usługa systemu Windows z FileSystemWatcher próbuje odczytać plik, zanim proces go zamknie.
freedeveloper
6

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

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żyć tego strumienia jak zwykle:

using (var stream = await FileStreamGetter.GetStreamAsync())
{
    Console.WriteLine(stream.Length);
}
Ivan Branets
źródło
3
Ile sekund do przepełnienia stosu z rekurencji GetStreamAsync()?
CAD bloke
@CADbloke, podniosłeś bardzo dobry punkt. Rzeczywiście moja próbka może mieć wyjątek przepełnienia stosu, na wypadek, gdyby plik nie był dostępny przez długi czas. W związku z tą odpowiedzią stackoverflow.com/questions/4513438 /... może to spowodować wyjątek w ciągu 5 godzin.
Ivan Branets
W związku z przypadkami użycia preferowane jest zgłoszenie wyjątku we / wy, jeśli powiedzmy, że 10 prób odczytu pliku nie powiodło się. Inną strategią może być wydłużenie czasu oczekiwania na sekundę po 10 nieudanych próbach. Możesz także użyć kombinacji obu.
Ivan Branets
Chciałbym (i zrobię) po prostu ostrzec użytkownika, że ​​plik jest zablokowany. Zazwyczaj sami go zamknęli, więc prawdopodobnie coś z tym zrobią. Albo nie.
CAD bloke
W niektórych przypadkach musisz użyć zasady ponawiania, ponieważ plik może nie być jeszcze gotowy. Wyobraź sobie aplikację komputerową do pobierania obrazów z jakiegoś folderu tymczasowego. Aplikacja rozpocznie pobieranie i jednocześnie otworzysz ten folder w Eksploratorze plików. Windows chce natychmiast utworzyć miniaturę i zablokować plik. Jednocześnie aplikacja próbuje zastąpić zablokowany obraz w innym miejscu. Otrzymasz wyjątek, jeśli nie użyjesz zasad ponownych prób.
Ivan Branets
4

jedynym sposobem, jaki znam, jest użycie wyjątkowego interfejsu API blokady Win32, który nie jest zbyt szybki, ale istnieją przykłady.

Większość ludzi, dla prostego rozwiązania tego problemu, po prostu próbuje / łapie / śpi pętle.

Luke Schafer
źródło
1
Nie można używać tego interfejsu API bez uprzedniego otwarcia pliku, w którym to momencie nie trzeba już.
Harry Johnston,
1
Kartoteka Schrodingera.
TheLastGIS
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");

Mam nadzieję że to pomoże!

juliański
źródło
10
Rzeczywista kontrola, którą wykonujesz, jest w porządku; umieszczenie go w funkcji jest mylące. NIE chcesz używać takiej funkcji przed otwarciem pliku. Wewnątrz funkcji plik jest otwierany, sprawdzany i zamykany. Następnie programista PRZYJMUJE, że plik jest W dalszym ciągu w porządku i próbuje go otworzyć. Jest to złe, ponieważ może zostać użyte i zablokowane przez inny proces, który został ustawiony w kolejce do otwarcia tego pliku. Pomiędzy pierwszym otwarciem (w celu sprawdzenia) a drugim otwarciem (w celu użycia) system operacyjny mógł zaprojektować proces i uruchomić inny proces.
Lakey
3

W powyższych odpowiedziach występuje problem polegający na tym, że jeśli plik został otwarty do zapisu w trybie FileShare.Read lub jeśli plik ma atrybut tylko do odczytu, kod nie będzie działał. To zmodyfikowane rozwiązanie działa najbardziej niezawodnie, pamiętając o dwóch rzeczach (tak jak w przypadku przyjętego rozwiązania):

  1. Nie będzie działać w przypadku plików otwartych w trybie udostępniania zapisu
  2. Nie bierze to pod uwagę problemów z wątkami, więc trzeba będzie je zablokować lub osobno poradzić sobie z problemami z wątkami.

Mając powyższe na uwadze, sprawdza, czy plik jest zablokowany do zapisu lub zablokowany, aby uniemożliwić odczyt :

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;
}
rboy
źródło
Nadal występuje ten sam problem co zaakceptowana odpowiedź - informuje tylko, czy plik został zablokowany przez inny proces w danym momencie , co nie jest użyteczną informacją. Do czasu powrotu funkcji wynik może być już nieaktualny!
Harry Johnston,
1
to prawda, można sprawdzić tylko w dowolnym momencie (lub subskrybować zdarzenia), przewaga tego podejścia nad przyjętym rozwiązaniem polega na tym, że można sprawdzić atrybut tylko do odczytu i blokadę zapisu i nie zwracać fałszywie dodatniego wyniku.
rboy
3

Poza działającymi 3 liniami i tylko w celach informacyjnych: Jeśli chcesz uzyskać pełne informacje - istnieje mały projekt w 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 pomógłby dowiedzieć się, który proces blokuje plik. Funkcja RmStartSession zawarta w pliku rstrtmgr.dll została użyta do utworzenia sesji menedżera restartu i zgodnie z wynikiem zwrotu tworzona jest nowa instancja obiektu Win32Exception. Po zarejestrowaniu zasobów w sesji Menedżera restartu za pomocą funkcji RmRegisterRescources wywoływana jest funkcja RmGetList w celu sprawdzenia, jakie aplikacje używają określonego pliku, poprzez wyliczenie tablicy RM_PROCESS_INFO .

Działa poprzez połączenie z „Restart Manager Session”.

Menedżer ponownego uruchamiania korzysta z listy zasobów zarejestrowanych w sesji, aby określić, które aplikacje i usługi muszą zostać zamknięte i ponownie uruchomione. Zasoby można rozpoznać po nazwach plików, krótkich nazwach usług lub strukturach RM_UNIQUE_PROCESS opisujących uruchomione aplikacje.

To może być trochę overengineered do indywidualnych potrzeb ... Ale jeśli to, co ty chcesz, idź i chwyć VS-projekt.

Bernhard
źródło
2

Z mojego doświadczenia wynika, że ​​zwykle chcesz to zrobić, a następnie „chronić” swoje pliki, aby zrobić coś wymyślnego, a następnie użyć plików „chronionych”. Jeśli masz tylko jeden plik, którego chcesz użyć w ten sposób, możesz skorzystać ze sztuczki wyjaśnionej w odpowiedzi przez Jeremy'ego Thompsona. Jeśli jednak spróbujesz to zrobić na wielu plikach (powiedzmy na przykład podczas pisania instalatora), czeka cię sporo bólu.

Bardzo eleganckim sposobem na rozwiązanie tego problemu jest fakt, że twój system plików nie pozwoli ci zmienić nazwy folderu, jeśli jeden z plików tam używanych jest używany. Trzymaj folder w tym samym systemie plików, a będzie działał jak urok.

Pamiętaj, że powinieneś być świadomy oczywistych sposobów, w jakie 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ć Moveniepowodzenie operacji. Oczywiście może pomóc tutaj poprawna obsługa błędów (MSDN).

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

W przypadku pojedynczych plików trzymałbym się sugestii blokowania opublikowanej przez Jeremy'ego Thompsona.

atlaste
źródło
Cześć, Ponieważ kolejność odpowiedzi zmienia się, możesz wyjaśnić, który post powyżej oznacza dla czytelników tego popularnego QA. Dzięki.
Jeremy Thompson
1
@JeremyThompson Masz rację, dziękuję, edytuję post. Użyłbym twojego rozwiązania, głównie z powodu twojego prawidłowego użycia FileSharei sprawdzania zamka.
atlaste
2

Oto kod, który, o ile mogę najlepiej powiedzieć, robi to samo co zaakceptowana odpowiedź, ale z mniejszym kodem:

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

Myślę jednak, że bardziej niezawodne jest to zrobić 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);
    }
cdiggins
źródło
2

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

Możesz 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
    }
}

Metoda fileLock.Acquire zwróci wartość true tylko wtedy, gdy można zablokować plik wyłączny dla tego obiektu. Ale aplikacja, która przesyła plik, musi to zrobić również w blokadzie plików. Jeśli obiekt jest niedostępny, metoda zwraca false.

Tomasz Żmuda
źródło
1
Proszę nie zamieszczać w odpowiedzi tylko jakiegoś narzędzia lub biblioteki. Przynajmniej pokaż, jak rozwiązuje problem w samej odpowiedzi.
paper1111
Dodano demo :) Przepraszamy @ paper1111
Tomasz Żmuda
1
Wymaga wszystkich procesów korzystających z pliku do współpracy. Jest mało prawdopodobne, aby dotyczyło pierwotnego problemu PO.
Harry Johnston,
2

Kiedyś musiałem przesyłać pliki PDF do archiwum kopii zapasowych online. Ale tworzenie kopii zapasowej nie powiedzie się, jeśli użytkownik otworzy plik w innym programie (np. Czytniku plików PDF). W pośpiechu spróbowałem kilka najlepszych odpowiedzi w tym wątku, ale nie udało mi się ich uruchomić. To, co zadziałało, to próba przeniesienia pliku PDF do własnego katalogu . Odkryłem, że to się nie powiedzie, jeśli plik zostanie otwarty w innym programie, a jeśli przeniesienie się powiedzie, operacja przywracania nie będzie wymagana, tak jak w przypadku przeniesienia do osobnego katalogu. Chcę opublikować moje podstawowe rozwiązanie na wypadek, gdyby okazało się przydatne w konkretnych przypadkach użycia przez innych.

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
}
Benjamin Curtis Drake
źródło
0

Interesuje mnie, czy to uruchomi jakiś refleks WTF. Mam proces, który tworzy, a następnie uruchamia dokument PDF z aplikacji konsoli. Miałem jednak 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 zginęła. Było to dość częste zdarzenie, ponieważ nazwy plików oparte są na numerach ofert sprzedaży.

Zamiast zawieść w tak nieskromny sposób, postanowiłem polegać na automatycznie zwiększanej wersji 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 poświęcić trochę więcej uwagi catchblokowi, aby upewnić się, że wychwytuję poprawne wyjątki IOException. Prawdopodobnie wyczyszczę również pamięć aplikacji podczas uruchamiania, ponieważ te pliki i tak mają być tymczasowe.

Zdaję sobie sprawę, że wykracza to poza kwestię PO polegającą na sprawdzeniu, czy plik jest w użyciu, ale to był problem, który chciałem rozwiązać, kiedy tu przybyłem, więc być może przyda się komuś innemu.

Vinney Kelly
źródło
0

Czy coś takiego mogłoby pomóc?

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

    }
}
Tadej
źródło
-2

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

Carra
źródło
@jcolebrand blokuje co? ten, który skopiowałeś? Czy ten, który umieściłeś w temp. Reż?
Cullub,
5
jeśli skopiujesz plik, oczekując, że nikt inny nie będzie nad nim pracował, a będziesz używać pliku tymczasowego, a następnie ktoś zablokuje go zaraz po skopiowaniu, potencjalnie utracisz dane.
jcolebrand
-3

Korzystam z tego obejścia, ale mam pewien przedział czasowy między momentem sprawdzenia blokady pliku za pomocą funkcji IsFileLocked a otwarciem pliku. W tym czasie jakiś inny wątek może otworzyć plik, więc otrzymam wyjątek 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 myślisz? Czy mogę coś zmienić? Może wcale nie musiałem używać funkcji IsFileBeingUsed?

Dzięki

zzfima
źródło
4
Co to jest IsFileBeingUsed? kod źródłowy o IsFileBeingUsed?
Kiquenet