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


Answers

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

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

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

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

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




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

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

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



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

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
}



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

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




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

شكر




الطريقة الوحيدة التي أعرفها هي استخدام واجهة برمجة التطبيقات (API) الخاصة بـ Win32 الحصرية التي لا تكون سريعة جدًا ، ولكن توجد أمثلة.

معظم الناس ، من أجل حل بسيط لهذا ، لمجرد محاولة / الصيد / النوم الحلقات.




تواجه الإجابات المقبولة أعلاه مشكلة إذا كان الملف مفتوحًا للكتابة باستخدام وضع 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;
}



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

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






Links