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

StackOverflow https://stackoverflow.com/questions/876473

سؤال

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

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

هل كانت مفيدة؟

المحلول

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

إبداعي:لقد استخدمت هذا الرمز منذ عدة سنوات ولم أواجه أي مشكلة معه.

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

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

نصائح أخرى

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

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

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

استخدم هذا للتحقق إذا تم تأمين ملف:

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

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

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

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

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

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

والطريقة الوحيدة التي يمكنني معرفة من هو استخدام Win32 والحصري قفل API وهو ليس سريع جدا، ولكن وجود أمثلة على ذلك.

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

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

وآمل أن يساعد هذا!

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

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

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

وبصرف النظر عن العمل 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 .بعد تسجيل الموارد في جلسة إدارة إعادة التشغيل عبر RmRegisterResources وظيفة، RmGetList يتم استدعاء الوظيفة للتحقق ما هي التطبيقات التي تستخدم ملفا معينا عن طريق التعداد ال RM_PROCESS_INFO مجموعة مصفوفة.

إنه يعمل عن طريق الاتصال بـ "إعادة تشغيل جلسة المدير".

يستخدم "إدارة إعادة التشغيل" قائمة الموارد المسجلة مع جلسة العمل ل تحديد التطبيقات والخدمات التي يجب إيقاف تشغيلها وإعادة تشغيلها. يمكن تحديد الموارد من خلال أسماء الملفات أو الأسماء المختصرة للخدمة أو الهياكل RM_UNIQUE_PROCESS التي تصف التطبيقات قيد التشغيل.

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

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

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

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

ويمكنك تثبيته من 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 سيعود صحيح فقط إذا كان يمكن قفل ملف الحصري لهذا الكائن. ولكن التطبيق الذي تحميل الملف يجب أن تفعل ذلك في قفل ملف أيضا. إذا الكائن ميتود لا يمكن الوصول إليها ترجع كاذبة.

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

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

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

لملفات فردية فما استقاموا لكم فاستقيموا العصا مع اقتراح قفل المرسلة بواسطة جيرمي تومبسون.

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

وبدلا من الفشل في مثل هذه الطريقة ungraceful، قررت أن تعتمد على تتزايد السيارات الإصدارات الملف:

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

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

وليت شيئا من هذا القبيل مساعدة؟

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

    }
}

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

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

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

والشكر

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top