c# حل - هل هناك طريقة للتحقق مما إذا كان الملف قيد الاستخدام؟




برنامج لان (15)

أنا أكتب برنامج في C # يحتاج إلى الوصول إلى ملف صورة بشكل متكرر. معظم الوقت يعمل ، ولكن إذا كان الكمبيوتر يعمل بسرعة ، فإنه سيحاول الوصول إلى الملف قبل حفظه مرة أخرى في نظام الملفات وإلقاء خطأ: "ملف قيد الاستخدام بواسطة عملية أخرى" .

أرغب في العثور على طريقة لذلك ، ولكن كل ما عندي من Google لم يسفر سوى عن إنشاء عمليات تحقق باستخدام معالجة الاستثناءات. هذا ضد ديني ، لذلك كنت أتساءل إذا كان أي شخص لديه طريقة أفضل للقيام بذلك؟


Answers

وهنا نسخة بوويرشيل من الجواب المقبول.

function IsFileLocked($filename) {

    $result = $false

    $fileinfo = [System.IO.FileInfo] (gi $filename).fullname

    try {
        $stream = $fileInfo.Open([System.IO.FileMode]"Open",[System.IO.FileAccess]"ReadWrite",[System.IO.FileShare]"None")
        $stream.Dispose()
    } catch [System.IO.IOException] {
        $result = $true
    }

    $result
}

هل يمكن أن تعاني من حالة سباق الخيط على هذا الأمر الذي يوجد أمثلة موثقة من هذا يجري استخدامه بمثابة ثغرة أمنية. إذا تحققت من أن الملف متاحًا ، ولكن حاول استخدامه واستخدمه ، فيمكنك عندئذٍ التخلص من هذه النقطة ، والتي يمكن أن يستخدمها مستخدم ضار لفرضها واستغلالها في شفرتك.

أفضل رهان هو محاولة الصيد / أخيرا الذي يحاول الحصول على مقبض الملف.

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

لقد واجهت مشكلة مماثلة وفعلت شيئًا يبدو ناجحًا ، فقد كان استخدام معالجة الاستثناء رغم ذلك ...

أضع عداد في لمحاولة الاستمرار 100 مرة لوقف حلقة لا نهاية لها.

انظر أدناه...

    private void uploadFiles(string filename)
    {
        try
        {
            string fromFileAndPath = Properties.Settings.Default.Path + "\\" + filename;
            string toFileAndPath = Properties.Settings.Default.CopyLocation + "\\" + filename;
            if (!File.Exists(toFileAndPath))
            {
                FileInfo imgInfo = new FileInfo(fromFileAndPath);
                bool copied = false;
                int counter = 0;
                while (!copied && counter < 100) //While was added as I was getting "The process cannot access the file because it is being used by another process" errors.
                {
                    try
                    {
                        counter++;
                        imgInfo.CopyTo(toFileAndPath);
                        copied = true;
                    }
                    catch
                    {
                        //If it cannot copy catch
                    }
                }
                if (counter > 100)
                    throw new Exception("Unable to copy file!");
                Thread.Sleep(1);
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show("An error occurred: " + ex.Message, "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

يمكنك استخدام مكتبتي للوصول إلى الملفات من تطبيقات متعددة.

يمكنك تثبيته من nuget: تثبيت حزمة 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 true فقط إذا كان يمكن قفل الملف الحصري لهذا الكائن. لكن التطبيق الذي يجب أن يفعله ملف التحميل في قفل الملف أيضًا. إذا كان الكائن يتعذر الوصول إلى metod false.


استخدم هذا للتحقق مما إذا كان الملف مغلقًا:

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

مجرد استخدام الاستثناء على النحو المنشود. اقبل أن الملف قيد الاستخدام وحاول مرة أخرى ، وبشكل متكرر حتى اكتمال الإجراء الخاص بك. هذا هو أيضا الأكثر كفاءة لأنك لا تضيع أي دورات التحقق من الحالة قبل التصرف.

استخدم الوظيفة أدناه ، على سبيل المثال

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

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

أتمنى أن يساعدك هذا!


في ما يلي بعض التعليمات البرمجية التي يمكن أن أحددها ، بقدر ما أستطيع أن أقول ، هي نفس الشيء مثل الإجابة المقبولة ولكن مع رمز أقل:

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

يمكنني استخدام هذا الحل ، ولكن لدي فترة زمنية بين عندما أتحقق من تأمين الملف مع الدالة 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 على الإطلاق؟

شكر


أنا مهتم لمعرفة ما إذا كان هذا يثير أي ردود الفعل 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 الصحيح. ربما أقوم أيضًا بمسح تخزين التطبيق عند بدء التشغيل نظرًا لأن هذه الملفات تهدف إلى أن تكون مؤقتة على أي حال.

إنني أدرك أن هذا يتجاوز نطاق سؤال سياسة العمليات الخاصة بالتحقق ببساطة من أن الملف قيد الاستخدام ولكن هذه كانت بالفعل المشكلة التي كنت أبحث عنها عندما وصلت إلى هنا لذا ربما يكون ذلك مفيدًا لشخص آخر.


في تجربتي ، فأنت تريد عادة القيام بذلك ، ثم "حماية" ملفاتك للقيام بشيء يتوهم ثم استخدام الملفات "المحمية". إذا كان لديك ملف واحد فقط تريد استخدامه على هذا النحو ، فيمكنك استخدام الحيلة الموضحة في الإجابة من قِبل Jeremy Thompson. ومع ذلك ، إذا حاولت القيام بذلك على الكثير من الملفات (على سبيل المثال ، عندما تقوم بكتابة برنامج تثبيت) ، فأنت تعاني قليلاً من الأذى.

وهناك طريقة أنيقة للغاية يمكن حل هذه المشكلة من خلال استخدام حقيقة أن نظام الملفات الخاص بك لن يسمح لك بتغيير اسم مجلد إذا كان أحد الملفات الموجودة قيد الاستخدام. احتفظ بالمجلد في نفس نظام الملفات وسيعمل مثل السحر.

لاحظ أنه يجب أن تكون على دراية بالطرق الواضحة التي يمكن استغلالها. بعد كل شيء ، لن يتم تأمين الملفات. لاحظ أيضًا أن هناك أسبابًا أخرى قد تؤدي إلى فشل عملية 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);
}

بالنسبة للملفات الفردية ، سألتزم باقتراح القفل الذي نشره جيريمي تومبسون.


ملاحظة محدثة على هذا الحل : ستفشل عملية التحقق باستخدام FileAccess.ReadWrite للقراءة فقط حتى يتم تعديل الحل للتحقق من FileAccess.Read . بينما يعمل هذا الحل لأن محاولة التحقق من FileAccess.Read ستفشل إذا كان الملف له قفل كتابة أو قراءة ، إلا أن هذا الحل لن يعمل إذا كان الملف لا يحتوي على قفل كتابة أو قراءة عليه ، تم فتح (للقراءة أو الكتابة) باستخدام FileShare.Read أو FileShare.Write.

ORIGINAL: لقد استخدمت هذا الرمز في السنوات العديدة الماضية ، ولم تكن لدي أي مشاكل معه.

افهم مدى ترددك في استخدام الاستثناءات ، ولكن لا يمكنك تجنبها طوال الوقت:

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

ربما يمكنك استخدام FileSystemWatcher ومشاهدة الحدث الذي تم تغييره.

لم أستخدم هذا بنفسي ، ولكن قد يكون من المفيد تصويره. إذا تبين أن نظام الملفات قد يكون ثقيلًا قليلاً لهذه الحالة ، فسأذهب لحلقة try / catch / sleep.


محاولة ونقل / نسخ الملف إلى دير مؤقت. إذا كنت تستطيع ، فإنه لا يوجد لديه قفل ويمكنك العمل بأمان في درجة الحرارة دون الحصول على أقفال. آخر مجرد محاولة نقله مرة أخرى في ثانية x.


فيما يتعلق os.linesep:

فيما يلي جلسة لمترجم فوري بلغة Python 2.7.1 غير محررة على نظام Windows:

Python 2.7.1 (r271:86832, Nov 27 2010, 18:30:46) [MSC v.1500 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.linesep
'\r\n'
>>> f = open('myfile','w')
>>> f.write('hi there\n')
>>> f.write('hi there' + os.linesep) # same result as previous line ?????????
>>> f.close()
>>> open('myfile', 'rb').read()
'hi there\r\nhi there\r\r\n'
>>>

على نظام التشغيل Windows:

كما هو متوقع ، لا ينتج os.linesep نفس النتيجة مثل '\n' . لا توجد طريقة يمكن أن تنتج نفس النتيجة. 'hi there' + os.linesep مساوية لـ 'hi there\r\n' ، والتي لا تعادل 'hi there\n' .

الأمر بسيط: استخدم \n التي سيتم ترجمتها تلقائيًا إلى os.linesep. ولقد كان بهذه البساطة منذ أول ميناء لبيثون إلى ويندوز.

ليس هناك فائدة من استخدام os.linesep على أنظمة غير Windows ، وينتج نتائج خاطئة على Windows.

لا تستخدم os.linesep!







c# .net file file-io file-locking