Чи є спосіб перевірити, чи використовується файл?


845

Я пишу програму на C #, якій потрібно неодноразово отримувати доступ до 1 файлу зображення. Більшу частину часу він працює, але якщо мій комп'ютер працює швидко, він спробує отримати доступ до файлу, перш ніж він буде збережений назад до файлової системи, і видасть помилку: "Файл використовується іншим процесом" .

Я хотів би знайти спосіб цього, але весь мій Googling лише створив перевірки, використовуючи обробку виключень. Це проти моєї релігії, тому мені було цікаво, чи хтось має кращий спосіб це зробити?


30
Гаразд, ви можете перевірити це, вивчивши всі відкриті ручки в системі. Однак, оскільки Windows - це багатозадачна операційна система, є ймовірність, що відразу після запуску коду визначити, чи файл відкритий, і ви вважаєте, що це не так, код процесу почне використовувати цей файл, а потім до моменту, коли ви намагаєтеся використовуючи його, ви отримуєте помилку. Але, нічого поганого в тому, щоб перевірити спочатку; просто не вважайте, що він не використовується, коли вам це потрібно.
BobbyShaftoe

3
Але саме для цього конкретного питання; Я рекомендую не вивчати ручки файлів і просто спробувати деяку задану кількість разів, скажімо, 3-5, перш ніж вийти з ладу.
BobbyShaftoe

Як генерується цей файл зображення? Чи можете ви зупинити / поспати / призупинити свою програму до завершення генерації? Це далеко не чудовий спосіб вирішення ситуації. Якщо ні, то я не думаю, що ви можете уникнути використання винятків.
Каттва

Чи не все використання винятків - це перевірка якогось припущення, роблячи щось потенційно небезпечне, водночас свідомо не виключаючи можливості відмови?
jwg

26
Ваша філософія погано розуміє винятки. Більшість людей думає, що винятки означають, що це святе лайно-приречення, щось не так Коли виняток означає .... виняток. Це означає, що трапилось щось виняткове, що вам потрібно "впоратися" (або взяти на облік). Можливо, ви хочете продовжувати спроби доступу до даних, можливо, користувачеві потрібно знати, що ви не можете встановити з'єднання. Що ти робиш? Ви обробляєте ConnectionFailedException та повідомляєте користувача, тому, можливо, через годину вони перестануть пробувати, і помітять, що кабель відключений.
Лі Лув'є

Відповіді:


543

Оновлено ПРИМІТКА щодо цього рішення : Не FileAccess.ReadWriteвдасться перевірити файли для файлів лише для читання, тому рішення було змінено для перевірки FileAccess.Read. Хоча це рішення працює, тому що спроба перевірити FileAccess.Readце не вдасться, якщо файл має блокування запису чи читання, однак це рішення не буде працювати, якщо у нього немає блокування запису чи читання, тобто він був відкритий (для читання або запису) за допомогою доступу до FileShare.Read або FileShare.Write.

ОРИГІНАЛ: Я використовував цей код протягом кількох останніх років, і в мене не було жодних проблем.

Зрозумійте свої вагання щодо використання винятків, але ви не можете їх уникати весь час:

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

60
Це чудове рішення, але я маю одне зауваження - можливо, ви не захочете відкривати файл у режимі доступу FileAccess.Read оскільки ReadWrite завжди вийде з ладу, якщо файл трапляється лише для читання.
adeel825

219
-1. Це погана відповідь, оскільки файл може бути заблокований іншим потоком / процесом після його закриття в IsFileLocked, і перш ніж ваш потік отримає шанс його відкрити.
Поліфун

16
Я думаю, що це чудова відповідь. Я використовую це як метод розширення а - ля public static bool IsLocked(this FileInfo file) {/*...*/}.
Манузор

53
@ChrisW: Вам може бути цікаво, що відбувається. Не турбуйтеся. Ви просто піддаєтеся
П'єр Лебеопін

16
@ChrisW Чому це погано. Ця громада тут, щоб вказати на добрі та погані відповіді. Якщо купа професіоналів помічає, що це погано, і приєднуйтесь до downvote, тоді на сайті WAI. І перш ніж ви отримаєте негатив, якщо ви прочитаєте цю статтю, вони говорять, що "підкріпити правильну відповідь", а не заперечувати неправильну. Ви хочете, щоб вони також пояснили свої коментарі в коментарях. Дякуємо, що познайомили мене з ще одним хорошим сайтом!
Лі Лув'є

569

Ви можете постраждати від умови перебігу потоку, у зв'язку з чим є задокументовані приклади використання цього засобу як вразливості безпеки. Якщо ви перевірите, чи файл доступний, але спробуйте його використати, ви можете кинути його в той момент, який зловмисний користувач може використати для примусового використання та використання у вашому коді.

Ваша найкраща ставка - спроба ловити / нарешті, яка намагається отримати обробку файлу.

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

124
+1. Не існує 100% безпечного способу "дізнатися, чи використовується файл", тому що через мілісекунди після того, як ви зробите перевірку, файл більше не використовується, або навпаки. Натомість ви просто відкриєте файл і використовуєте його, якщо немає винятків.
Седат Капаноглу

8
Дуже погано .NET не підтримує CAS. Щось на кшталт TryOpenFile (Ref FileHandle), який повертає успіх / невдачу. Завжди має бути обхід, а не покладатися лише на обробку виключень. Цікаво, як це робить Microsoft Office.
TamusJRoyce

2
Тут важливо зрозуміти, що цей API просто використовує API Windows, щоб отримати обробку файлів. Як такий, їм потрібно перекласти код помилки, отриманий з API API, і перетворити його на виняток, щоб кинути. Ми маємо обробку винятків у .Net, то чому б не використовувати його. Таким чином ви можете записати чистий шлях вперед у свій код і залишити обробку помилок окремим кодовим шляхом.
Спенс

36
Оператор, що використовує, полягає у тому, щоб забезпечити закриття потоку після того, як я закінчу. Я думаю, ви побачите, що в (({{) менше символів, ніж спробуйте {} нарешті {obj.Dispose ()}. Ви також виявите, що тепер вам потрібно оголосити посилання на ваш об'єкт поза використанням оператора, що більше вводить текст. Якщо у вас явний інтерфейс, вам також доведеться передати. Нарешті, ви хочете розпоряджатися якнайшвидше, і, нарешті, логіка може мати інтерфейс користувача або будь-які інші тривалі дії, які мають мало спільного з викликом IDispose. </rant>
Спенс

2
це не заперечує того факту, що вам потрібно оголосити свій об'єкт поза спробою і виразно викликати розпорядження, що з використанням робить для вас і означає те саме.
Спенс

92

Використовуйте це, щоб перевірити, чи файл заблокований:

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

З міркувань продуктивності рекомендую прочитати вміст файлу в одній операції. Ось кілька прикладів:

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

Спробуйте самі:

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

8
Я б сказав, якщо б там не було так багато "магічних чисел" en.wikipedia.org/wiki/Magic_number_(programming)
Kris

3
Я мав на увазі порівняння errorCode, а не бітових зрушень. хоча зараз ви це згадуєте ...
Кріс

1
Ваш Catch повинен бути включений IOException, а не загальний, Exceptionа потім тест на тип.
Асколейн

3
@JeremyThompson, на жаль, ви ставите конкретні IOExceptionпісля загального. Загальне зловить усе, що проходить повз, і конкретні IOExceptionзавжди будуть самотніми. Просто поміняйте два місця.
Асколейн

Мені подобається це рішення. Ще одна пропозиція: всередині улову як інше, якщо if (IsFileLocked (ex)) я б кинув екс. Потім буде оброблено випадок, коли файл не існує (або будь-який інший IOException), передавши виняток.
шиндиго

7

Просто використовуйте виняток за призначенням. Прийміть, що файл використовується, і повторіть спробу, повторно, доки ваша дія не буде завершена. Це також є найбільш ефективним, оскільки ви не витрачаєте жодних циклів, перевіряючи стан перед тим, як діяти.

Наприклад, скористайтеся функцією, наведеною нижче

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

Метод багаторазового використання, який закінчується через 2 секунди

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

6

Можливо, ви можете використовувати FileSystemWatcher і спостерігати за подіями Changed.

Я сам цього не використовував, але, можливо, варто його зняти. Якщо вигляд файлової системи виявиться трохи важким для цього випадку, я б пішов на цикл try / catch / sleep.


1
Використання FileSystemWatcher не допомагає, оскільки створені та змінені події піднімаються на початку створення / зміни файлу. Навіть невеликі файли потребують більше часу для написання та закриття операційною системою, ніж додатку .NET потрібно запускати через зворотний виклик FileSystemEventHandler. Це так сумно, але немає іншого варіанту, ніж оцінити час очікування до доступу до файлу або запустити цикли винятків ...

FileSystemWatcher не дуже добре обробляє одночасно багато змін, тому будьте обережні.
Бен Ф

1
BTW, ви помітили, хлопці, під час налагодження та перегляду тем, що MS називає власну FSW "FileSystemWather"? Що все-таки пізніше?
devlord

Саме у мене виникла ця проблема, оскільки служба Windows з FileSystemWatcher намагається прочитати файл, перш ніж процес його закрив.
вільнорозробник

6

Ви можете повернути завдання, яке дає вам потік, як тільки воно стане доступним. Це спрощене рішення, але це хороший вихідний пункт. Це безпечно для ниток.

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

Ви можете використовувати цей потік як завжди:

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

3
Скільки секунд, поки стек переповнюється від рекурсії в GetStreamAsync()?
CAD заблокували

@CADbloke, ви підняли дуже хороший момент. Дійсно, мій зразок може мати виняток переповнення стека, якщо файл тривалий час недоступний. Пов’язана з цією відповіддю stackoverflow.com/questions/4513438/… , виняток може підняти за 5 годин.
Іван Бранець

Що стосується ваших випадків використання, бажано викинути виключення вводу / виводу, якщо скажімо, 10 спроб прочитати файл не вдалося. Інша стратегія може збільшити час очікування на секунду, як тільки 10 спроб не вдалося. Ви також можете використовувати суміш обох.
Іван Бранець

Я б (і це робив) просто попередив користувача, що файл заблокований. Зазвичай вони самі це заблокували, тому, ймовірно, щось з цим зроблять. Чи ні.
CAD заблокували

У деяких випадках потрібно використовувати політику повторного спроби, оскільки файл може бути ще не готовий. Уявіть собі настільний додаток для завантаження зображень у якусь тимчасову папку. Додаток починає завантажувати і одночасно відкривати цю папку у файлі провідника. Windows хоче негайно створити мініатюру та заблокувати файл. У той же час ваша програма намагається замінити заблоковане зображення на якесь інше місце. Ви отримаєте виняток, якщо не будете використовувати політику повторної спроби.
Іван Бранець

4

Єдиний спосіб, про який я знаю, - це використовувати ексклюзивний API блокування Win32, який не надто швидкий, але приклади існують.

Більшість людей для простого вирішення цього питання просто спробувати / ловити / спати петлі.


1
Ви не можете використовувати цей API, не відкривши файл, після чого вам більше не потрібно.
Гаррі Джонстон

1
Файл Шродінгера.
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");

Сподіваюся, це допомагає!


10
Фактична перевірка, яку ви виконуєте, нормальна; введення його всередині функції вводить в оману. Ви НЕ бажаєте використовувати подібну функцію перед відкриттям файлу. Всередині функції файл відкривається, перевіряється та закривається. Тоді програміст ЗАБЕЗПЕЧУЄ файл, ЩО ВИНАГАЛЬНИЙ у використанні та намагається відкрити його для використання. Це погано, оскільки його можна було б використовувати та заблокувати іншим процесом, який був у черзі для відкриття цього файлу. Між першим відкриттям (для перевірки) і другим відкриттям (для використання) ОС могла б зняти з роботи ваш процес і може запустити інший процес.
Лакей

3

Прийняті вище відповіді зазнають проблеми, якщо якщо файл був відкритий для запису в режимі FileShare.Read або якщо у файлу є атрибут "Лише для читання", код не працює. Це модифіковане рішення працює найбільш надійно, маючи на увазі дві речі (як і для прийнятого рішення):

  1. Він не працюватиме для файлів, відкритих у режимі спільного запису
  2. Це не враховує проблеми з нитками, тому вам потрібно буде заблокувати їх або вирішити проблеми з ниткою окремо.

Маючи на увазі вищесказане, це перевіряє, чи файл заблокований для запису чи заблокований, щоб запобігти читанню :

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

Проблема все ще має таку, що і прийнята відповідь - вона лише повідомляє, чи був файл заблокований іншим процесом в один конкретний момент часу , що не є корисною інформацією. На момент повернення функції результат може бути застарілим!
Гаррі Джонстон

1
це правда, можна перевірити лише в будь-який момент часу (або підписатися на події), перевага такого підходу над прийнятим рішенням полягає в тому, що він може перевірити атрибут лише для читання та блокування запису, а не повернути помилковий позитив.
rboy

3

Окрім роботи з 3-х вкладишами та лише для ознайомлення: Якщо ви хочете отримати повну інформацію, то в Microsoft Dev Center є невеликий проект:

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

Зі вступу:

Зразок коду C #, розроблений в .NET Framework 4.0, допоможе з'ясувати, який процес має блокування файлу. Функція RmStartSession, яка включена до rstrtmgr.dll, була використана для створення сеансу менеджера перезавантаження і відповідно до результату повернення створюється новий примірник об’єкта Win32Exception. Після реєстрації ресурсів на сеансі диспетчера перезапуску через функцію RmRegisterRescources функція RmGetList викликається, щоб перевірити, які програми використовують конкретний файл, перерахувавши масив RM_PROCESS_INFO .

Він працює, підключившись до "Сесії перезавантаження менеджера".

Менеджер перезапуску використовує список ресурсів, зареєстрованих під час сеансу, щоб визначити, які програми та послуги потрібно вимкнути та перезапустити. Ресурси можна ідентифікувати за іменами файлів, короткими іменами служби або структурами RM_UNIQUE_PROCESS, які описують запущені програми.

Це може бути трохи переосмислено для ваших конкретних потреб ... Але якщо ви цього хочете, продовжуйте і захоплюйте vs-проект.


2

На мій досвід, ти зазвичай хочеш це зробити, потім "захистити" свої файли, щоб зробити щось фантазійне, а потім використовувати "захищені" файли. Якщо у вас є лише один файл, який ви хочете використовувати таким чином, ви можете скористатися хитрістю, поясненою у відповіді Джеремі Томпсона. Однак якщо ви намагаєтеся це зробити у багатьох файлах (скажімо, наприклад, коли ви пишете інсталятор), вам дуже шкодить.

Дуже елегантний спосіб вирішити це, використовуючи той факт, що ваша файлова система не дозволить вам змінити ім'я папки, якщо один з файлів там використовується. Зберігайте папку в тій же файловій системі, і вона працюватиме як шарм.

Зауважте, що ви повинні знати про очевидні способи, якими цим можна скористатися. Зрештою, файли не будуть заблоковані. Також майте на увазі, що існують й інші причини, які можуть призвести до того, що ваша Moveоперація закінчиться. Очевидно правильне поводження з помилками (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);
}

Для окремих файлів я б дотримувався пропозиції блокування, опублікованої Джеремі Томпсоном.


Привіт, Оскільки в порядку зміни відповідей ви можете уточнити, яку публікацію вище ви маєте на увазі для читачів цієї популярної якості. Дякую.
Джеремі Томпсон

1
@JeremyThompson Ти маєш рацію, дякую, я відредагую публікацію. Я б використовував рішення від вас, головним чином через ваше правильне використання FileShareта перевірку на наявність замка.
atlaste

2

Ось якийсь код, який, наскільки я можу найкраще сказати, робить те саме, що і прийнята відповідь, але з меншим кодом:

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

Однак я вважаю, що це надійніше робити це наступним чином:

    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

Ви можете використовувати мою бібліотеку для доступу до файлів із декількох додатків.

Ви можете встановити його з nuget: Install-Package Xabe.FileLock

Якщо ви хочете отримати додаткову інформацію про неї, перевірте 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 поверне вірно лише в тому випадку, якщо можна заблокувати ексклюзивний файл для цього об’єкта. Але додаток, який завантажує файл, повинен робити це і в блокуванні файлів. Якщо об'єкт недоступний, метод повертає значення false.


1
Будь ласка, не просто публікуйте якийсь інструмент чи бібліотеку як відповідь. Принаймні продемонструйте, як вона вирішує проблему у самій відповіді.
папір1111

Додано демо :) Вибачте @ paper1111
Tomasz

1
Потрібна співпраця всіх процесів, які використовують файл. Навряд чи можна застосувати до початкової проблеми ОП.
Гаррі Джонстон

2

Мені колись потрібно було завантажити PDF-файли в онлайн-архів резервного копіювання. Але резервне копіювання не вдалося б, якби користувач відкрив файл в іншій програмі (наприклад, PDF Reader). Я поспішив, я спробував кілька найкращих відповідей у ​​цій темі, але не зміг змусити їх працювати. Що для мене працювало, намагалася перемістити файл PDF у свою власну директорію . Я виявив, що це не вдасться, якщо файл був відкритий в іншій програмі, і якби переміщення було успішним, операція відновлення не потрібна, як це було б, якби було переміщено в окремий каталог. Я хочу розмістити своє основне рішення у випадку, якщо воно може бути корисним для конкретних випадків використання інших.

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
}

0

Мені цікаво подивитися, чи це викликає рефлекси WTF. У мене є процес, який створює та згодом запускає PDF-документ із консольного додатка. Однак я мав справу з недоліком, коли, якщо користувач повинен запустити процес кілька разів, генеруючи один і той же файл, не закриваючи попередньо створений файл, додаток кине виняток і загине. Це було досить частою справою, оскільки назви файлів базуються на номерах продажів.

Замість того, щоб вийти з ладу таким непривабливим чином, я вирішив покластися на автоматичне збільшення версії файлів:

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

Можливо, в catchблоці може бути приділено ще трохи уваги, щоб переконатися, що я вловлюю правильні IOException (s). Я, ймовірно, також очистить сховище додатків при запуску, оскільки ці файли в будь-якому випадку призначені тимчасовими.

Я усвідомлюю, що це виходить за рамки питання ОП про просто перевірити, чи файл використовується, але це справді була проблема, яку я шукав, щоб вирішити, коли приїхав сюди, так що, можливо, це стане в нагоді комусь іншому.


0

Чи допоможе щось подібне?

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

    }
}

-2

Спробуйте перемістити / скопіювати файл у темп. Якщо ви можете, він не має блокування, і ви можете спокійно працювати в темп-режимі, не отримуючи замків. Ще просто спробуйте перемістити його знову через х секунд.


@jcolebrand блокує що? той, який ви скопіювали? Або ту, яку ви поклали в темп реж?
Cullub

5
якщо ви скопіюєте файл, очікуючи, що ніхто більше не буде над ним працювати, і ви збираєтесь використовувати тимчасовий файл, а потім хтось заблокує його відразу після його копіювання, ви потенційно втратили дані.
jcolebrand

-3

Я використовую це вирішення, але у мене є часовий проміжок часу, коли я перевіряю блокування файлу за допомогою функції IsFileLocked та коли я відкриваю файл. У цей проміжок часу якийсь інший потік може відкрити файл, тож я отримаю IOException.

Отже, я додав для цього додатковий код. У моєму випадку я хочу завантажити 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!!!!!");
            }
        }

Як ти гадаєш? Чи можу я щось змінити? Можливо, мені взагалі не довелося використовувати функцію IsFileBeingUsed?

Дякую


4
що IsFileBeingUsed? вихідний код про IsFileBeingUsed?
Кікенет
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.