Apakah ada cara untuk memeriksa apakah suatu file sedang digunakan?

846

Saya sedang menulis sebuah program dalam C # yang perlu berulang kali mengakses 1 file gambar. Sebagian besar waktu berfungsi, tetapi jika komputer saya berjalan cepat, ia akan mencoba mengakses file sebelum disimpan kembali ke sistem file dan membuat kesalahan: "File digunakan oleh proses lain" .

Saya ingin mencari jalan keluar, tetapi semua Googling saya hanya menghasilkan membuat cek dengan menggunakan penanganan pengecualian. Ini bertentangan dengan agama saya, jadi saya bertanya-tanya apakah ada cara yang lebih baik untuk melakukannya?

Dawsy
sumber
30
Baiklah, Anda dapat mengujinya dengan memeriksa semua pegangan terbuka pada sistem. Namun, karena Windows adalah sistem operasi multitasking, ada kemungkinan bahwa setelah Anda menjalankan kode untuk menentukan apakah file terbuka dan Anda menganggapnya tidak, kode proses mulai menggunakan file itu, kemudian pada saat Anda mencoba untuk gunakan itu, Anda menerima kesalahan. Tapi, tidak ada yang salah dengan memeriksa dulu; tapi jangan menganggap itu tidak digunakan ketika Anda benar-benar membutuhkannya.
BobbyShaftoe
3
Tetapi hanya untuk masalah khusus ini; Saya akan merekomendasikan tidak memeriksa file menangani dan hanya mencoba beberapa kali, katakanlah 3-5 sebelum gagal.
BobbyShaftoe
Bagaimana file gambar ini dihasilkan? Bisakah Anda berhenti / tidur / jeda program Anda sampai generasi selesai? Itu sejauh ini merupakan cara yang unggul untuk menangani situasi. Jika tidak, maka saya tidak berpikir Anda bisa menghindari menggunakan penanganan pengecualian.
Catchwa
Bukankah semua pengecualian menggunakan cek pada beberapa asumsi dengan melakukan sesuatu yang berpotensi berbahaya sementara sengaja tidak mengesampingkan kemungkinan kegagalan?
jwg
26
Filosofi Anda memiliki pemahaman yang buruk tentang pengecualian. Kebanyakan orang berpikir pengecualian berarti suci-omong kosong-mati-mati-mati-mati. Ketika pengecualian berarti .... pengecualian. Ini berarti sesuatu yang luar biasa terjadi yang perlu Anda "tangani" (atau pertanggungjawabkan). Mungkin Anda ingin terus mencoba kembali untuk akses data, mungkin pengguna perlu tahu bahwa Anda tidak bisa mendapatkan koneksi. Apa yang kamu kerjakan? Anda menangani ConnectionFailedException dan memberi tahu pengguna, jadi mungkin, mereka akan berhenti mencoba setelah satu jam, dan melihat kabel dicabut.
Lee Louviere

Jawaban:

543

CATATAN yang diperbarui pada solusi ini : Memeriksa FileAccess.ReadWriteakan gagal untuk file Read-Only sehingga solusi telah dimodifikasi untuk memeriksa FileAccess.Read. Meskipun solusi ini berfungsi karena mencoba memeriksa FileAccess.Readakan gagal jika file memiliki kunci Tulis atau Baca di atasnya, namun, solusi ini tidak akan berfungsi jika file tidak memiliki kunci Tulis atau Baca di dalamnya, yaitu telah dibuka (untuk membaca atau menulis) dengan akses FileShare.Read atau FileShare.Write.

ASLI: Saya telah menggunakan kode ini selama beberapa tahun terakhir, dan saya belum punya masalah dengannya.

Pahami keraguan Anda tentang penggunaan pengecualian, tetapi Anda tidak bisa menghindarinya sepanjang waktu:

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
sumber
60
Ini adalah solusi yang bagus, tetapi saya punya satu komentar - Anda mungkin tidak dapat membuka File dengan mode akses FileAccess. Baca sejak ReadWrite akan selalu gagal jika file tersebut hanya untuk dibaca-saja.
adeel825
220
-1. Ini adalah jawaban yang buruk, karena file dapat dikunci oleh utas / proses lain setelah ditutup di IsFileLocked, dan sebelum utas Anda mendapat kesempatan untuk membukanya.
Polyfun
16
Saya pikir ini adalah jawaban yang bagus. Saya menggunakan ini sebagai metode ekstensi á la public static bool IsLocked(this FileInfo file) {/*...*/}.
Manuzor
54
@ ChrisW: Anda mungkin bertanya-tanya apa yang sedang terjadi. Jangan khawatir. Anda hanya menjadi sasaran kemarahan komunitas WTF Harian: thedailywtf.com/Comments/…
Pierre Lebeaupin
16
@ Chris Mengapa itu hal yang buruk. Komunitas ini ada di sini untuk menunjukkan jawaban yang baik dan buruk. Jika sekelompok profesional melihat ini adalah hal yang buruk, dan bergabung untuk downvote, maka situsnya adalah WAI. Dan sebelum Anda menjadi negatif, jika Anda membaca artikel itu, mereka mengatakan untuk "memilih jawaban yang benar" bukan mengungguli yang salah. Apakah Anda ingin mereka menjelaskan upvote mereka dalam komentar juga. Terima kasih telah memperkenalkan saya ke situs lain yang bagus!
Lee Louviere
569

Anda dapat menderita dari kondisi ras thread di mana ada contoh-contoh yang terdokumentasi dari ini digunakan sebagai kerentanan keamanan. Jika Anda memeriksa apakah file tersebut tersedia, tetapi kemudian coba dan gunakan, Anda bisa melempar pada titik itu, yang bisa digunakan oleh pengguna jahat untuk memaksa dan mengeksploitasi kode Anda.

Taruhan terbaik Anda adalah try catch / akhirnya yang mencoba untuk mendapatkan file menangani.

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.
}
Tempat menyimpan bahan makanan
sumber
124
+1. Tidak ada 100% cara aman untuk "mengetahui apakah file sedang digunakan" karena milidetik setelah Anda melakukan pemeriksaan, file tersebut mungkin tidak digunakan lagi, atau sebaliknya. Sebagai gantinya, Anda hanya membuka file dan menggunakannya jika tidak ada pengecualian.
Sedat Kapanoglu
8
Sayang sekali. NET tidak mendukung CAS. Sesuatu seperti, TryOpenFile (Ref FileHandle) yang mengembalikan keberhasilan / kegagalan. Harus selalu ada penyelesaian di sekitar tidak bergantung pada penanganan pengecualian saja. Saya bertanya-tanya bagaimana Microsoft Office melakukannya.
TamusJRoyce
2
Hal utama yang perlu dipahami di sini adalah bahwa API ini hanya menggunakan windows API untuk mendapatkan penanganan file. Karena itu mereka perlu menerjemahkan kode kesalahan yang diterima dari C API dan membungkusnya dengan pengecualian untuk melempar. Kami memiliki penanganan perkecualian di .Net jadi mengapa tidak menggunakannya. Dengan begitu Anda bisa menulis jalur maju bersih di kode Anda dan meninggalkan kesalahan penanganan di jalur kode terpisah.
Spence
36
Pernyataan menggunakan adalah untuk memastikan aliran ditutup setelah saya selesai. Saya pikir Anda akan menemukan bahwa menggunakan () {} lebih sedikit karakter daripada mencoba {} akhirnya {obj.Dispose ()}. Anda juga akan menemukan bahwa Anda sekarang harus mendeklarasikan referensi objek Anda di luar pernyataan using, yang lebih banyak mengetik. Jika Anda memiliki antarmuka eksplisit, Anda juga harus melakukan casting. Akhirnya Anda ingin membuang ASAP, dan akhirnya logika mungkin memiliki UI atau tindakan jangka panjang lainnya yang tidak ada hubungannya dengan memanggil IDispose. </rant>
Spence
2
itu tidak meniadakan fakta bahwa Anda harus mendeklarasikan objek Anda di luar coba dan harus secara eksplisit memanggil buang, yang menggunakan tidak untuk Anda dan berarti hal yang sama.
Spence
92

Gunakan ini untuk memeriksa apakah file terkunci:

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

Untuk alasan kinerja, saya sarankan Anda membaca konten file dalam operasi yang sama. Berikut ini beberapa contohnya:

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

Cobalah sendiri:

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
sumber
8
Saya akan menang jika tidak ada begitu banyak "angka ajaib" di sana en.wikipedia.org/wiki/Magic_number_(programming)
Kris
3
Saya merujuk pada perbandingan errorCode, bukan perubahan bit. meskipun sekarang Anda menyebutkannya ...
Kris
1
Catch Anda harus aktif IOException, bukan pada umumnya Exceptiondan kemudian tes pada tipe.
Askolein
3
@JeremyThompson sayangnya Anda menempatkan spesifik IOExceptionsetelah yang umum. Yang umum akan menangkap semua yang lewat dan spesifik IOExceptionakan selalu kesepian. Tukar saja keduanya.
Askolein
Saya suka solusi ini. Satu saran lain: di dalam tangkapan sebagai yang lain ke if (IsFileLocked (ex)) saya akan membuang mantan. Ini kemudian akan menangani kasus di mana file tidak ada (atau IOException lainnya) dengan melemparkan pengecualian.
shindigo
7

Cukup gunakan pengecualian sebagaimana dimaksud. Terima bahwa file sedang digunakan dan coba lagi, berulang kali hingga tindakan Anda selesai. Ini juga yang paling efisien karena Anda tidak membuang siklus memeriksa keadaan sebelum bertindak.

Gunakan fungsi di bawah ini, misalnya

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

Metode yang dapat digunakan kembali yang habis setelah 2 detik

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);
}
kode kernow
sumber
6

Mungkin Anda bisa menggunakan FileSystemWatcher dan menonton acara yang Diubah.

Saya belum pernah menggunakan ini sendiri, tetapi mungkin layak dicoba. Jika filesystemwatcher ternyata agak berat untuk kasus ini, saya akan mencoba loop coba / tangkap / tidur.

Karl Johan
sumber
1
Menggunakan FileSystemWatcher tidak membantu, karena peristiwa Dibuat dan Diubah meningkat pada awal pembuatan / perubahan file. Bahkan file kecil memerlukan lebih banyak waktu untuk ditulis dan ditutup oleh sistem operasi daripada aplikasi .NET perlu dijalankan melalui FileSystemEventHandler Callback. Ini sangat menyedihkan, tetapi tidak ada pilihan lain selain memperkirakan waktu tunggu sebelum mengakses file atau mengalami loop pengecualian ...
FileSystemWatcher tidak menangani banyak perubahan pada saat yang sama dengan sangat baik, jadi berhati-hatilah dengan itu.
Ben F
1
BTW, sudahkah kalian perhatikan saat men-debug dan menonton utas yang MS sebut FSW mereka sendiri "FileSystemWather"? Apa itu wather?
devlord
Justru saya mendapat masalah ini karena layanan windows dengan FileSystemWatcher mencoba membaca file sebelum proses menutupnya.
freedeveloper
6

Anda dapat mengembalikan tugas yang memberi Anda streaming segera setelah tersedia. Ini solusi yang disederhanakan, tetapi ini adalah titik awal yang baik. Itu aman dari utas.

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

Anda dapat menggunakan aliran ini seperti biasa:

using (var stream = await FileStreamGetter.GetStreamAsync())
{
    Console.WriteLine(stream.Length);
}
Ivan Branets
sumber
3
Berapa detik hingga tumpukan meluap dari rekursi GetStreamAsync()?
CAD berbicara
@CADbloke, Anda telah mengangkat poin yang sangat bagus. Memang contoh saya mungkin memiliki pengecualian stack overflow, kalau-kalau file tidak tersedia untuk waktu yang lama. Terkait dengan jawaban ini stackoverflow.com/questions/4513438/… , ini dapat meningkatkan pengecualian dalam 5 jam.
Ivan Branets
Terkait dengan kasus penggunaan Anda, lebih baik untuk melemparkan pengecualian I / O jika katakanlah 10 upaya untuk membaca file telah gagal. Strategi lain mungkin meningkatkan waktu tunggu sesaat setelah 10 upaya gagal. Anda juga dapat menggunakan campuran keduanya.
Ivan Branets
Saya akan (dan melakukan) cukup mengingatkan pengguna file terkunci. Mereka umumnya menguncinya sendiri sehingga mereka mungkin akan melakukan sesuatu. Atau tidak.
CAD berbicara
Dalam beberapa kasus, Anda perlu menggunakan kebijakan coba lagi karena file mungkin belum siap. Bayangkan aplikasi desktop untuk mengunduh gambar di beberapa folder sementara. Aplikasi mulai mengunduh dan pada saat yang sama Anda membuka folder ini di file explorer. Windows ingin segera membuat thumbnail dan mengunci file. Pada saat yang sama, aplikasi Anda mencoba mengganti gambar yang terkunci ke tempat lain. Anda akan menerima pengecualian jika Anda tidak menggunakan kebijakan coba lagi.
Ivan Branets
4

satu-satunya cara saya tahu adalah dengan menggunakan API kunci Win32 eksklusif yang tidak terlalu cepat, tetapi ada contoh.

Kebanyakan orang, untuk solusi sederhana untuk ini, cukup mencoba / menangkap / tidur loop.

Luke Schafer
sumber
1
Anda tidak dapat menggunakan API ini tanpa terlebih dahulu membuka file, pada titik mana Anda tidak perlu lagi.
Harry Johnston
1
File Schrodinger.
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");

Semoga ini membantu!

Julian
sumber
10
Pemeriksaan aktual yang Anda lakukan baik-baik saja; memasukkannya ke dalam fungsi menyesatkan. Anda TIDAK ingin menggunakan fungsi seperti ini sebelum membuka file. Di dalam fungsi, file dibuka, diperiksa, dan ditutup. Kemudian programmer MEMASANG file tersebut MASIH ok untuk digunakan dan mencoba membukanya untuk digunakan. Ini buruk karena bisa digunakan dan dikunci oleh proses lain yang sedang antri untuk membuka file ini. Antara pertama kali dibuka (untuk pengecekan) dan kedua kalinya dibuka (untuk digunakan), OS bisa menjadwal ulang proses Anda dan bisa menjalankan proses lain.
Lakey
3

Jawaban yang diterima di atas mengalami masalah di mana jika file telah dibuka untuk menulis dengan mode FileShare.Read atau jika file memiliki atribut Read-Only kode tidak akan berfungsi. Solusi yang dimodifikasi ini berfungsi paling andal, dengan dua hal yang perlu diingat (seperti juga untuk solusi yang diterima):

  1. Ini tidak akan berfungsi untuk file yang telah dibuka dengan mode berbagi tulis
  2. Ini tidak mempertimbangkan masalah threading akun sehingga Anda perlu menguncinya atau menangani masalah threading secara terpisah.

Ingatlah hal di atas, ini memeriksa apakah file dikunci untuk ditulis atau dikunci untuk mencegah pembacaan :

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
sumber
Masih memiliki masalah yang sama dengan jawaban yang diterima - hanya memberi tahu Anda apakah file dikunci oleh proses lain pada satu waktu tertentu , yang bukan informasi yang berguna. Pada saat fungsi telah kembali, hasilnya mungkin sudah kedaluwarsa!
Harry Johnston
1
itu benar, seseorang hanya dapat memeriksa pada waktu tertentu (atau berlangganan acara), keuntungan dari pendekatan ini atas solusi yang diterima adalah bahwa ia dapat memeriksa atribut hanya baca dan kunci tulis dan tidak mengembalikan positif palsu.
rboy
3

Selain bekerja 3-liner dan hanya untuk referensi: Jika Anda ingin full blown - ada sedikit proyek di Microsoft Dev Center:

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

Dari Pendahuluan:

Kode sampel C # yang dikembangkan dalam .NET Framework 4.0 akan membantu dalam mencari tahu proses mana yang memiliki kunci pada file. Fungsi RmStartSession yang termasuk dalam rstrtmgr.dll telah digunakan untuk membuat sesi manajer restart dan sesuai dengan hasil kembali instance baru objek Win32Exception dibuat. Setelah mendaftarkan sumber daya ke sesi Restart Manager melalui fungsi RmRegisterRescources , fungsi RmGetList dipanggil untuk memeriksa aplikasi apa yang menggunakan file tertentu dengan menyebutkan array RM_PROCESS_INFO .

Ini bekerja dengan menghubungkan ke "Restart Manager Session".

Restart Manager menggunakan daftar sumber daya yang terdaftar dalam sesi untuk menentukan aplikasi dan layanan mana yang harus dimatikan dan dimulai kembali. Sumber daya dapat diidentifikasi dengan nama file, nama pendek layanan, atau struktur RM_UNIQUE_PROCESS yang menjelaskan aplikasi yang sedang berjalan.

Mungkin sedikit overengineered untuk kebutuhan khusus Anda ... Tetapi jika itu yang Anda inginkan, silakan dan ambil vs-proyek.

Bernhard
sumber
2

Dalam pengalaman saya, Anda biasanya ingin melakukan ini, kemudian 'melindungi' file Anda untuk melakukan sesuatu yang mewah dan kemudian menggunakan file 'dilindungi'. Jika Anda hanya memiliki satu file yang ingin Anda gunakan seperti ini, Anda dapat menggunakan trik yang dijelaskan dalam jawaban oleh Jeremy Thompson. Namun, jika Anda mencoba melakukan ini pada banyak file (katakanlah, misalnya ketika Anda sedang menulis installer), Anda akan terluka.

Cara yang sangat elegan ini dapat dipecahkan adalah dengan menggunakan fakta bahwa sistem file Anda tidak akan memungkinkan Anda untuk mengubah nama folder jika salah satu file di sana sedang digunakan. Simpan folder dalam sistem file yang sama dan itu akan berfungsi seperti pesona.

Harap dicatat bahwa Anda harus mengetahui cara-cara yang jelas ini dapat dieksploitasi. Lagi pula, file tidak akan dikunci. Perlu diketahui juga bahwa ada alasan lain yang dapat menyebabkan Moveoperasi Anda gagal. Jelas penanganan kesalahan yang tepat (MSDN) dapat membantu di sini.

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

Untuk file individual saya akan tetap dengan saran penguncian diposting oleh Jeremy Thompson.

atlaste
sumber
Hi, Karena urutan perubahan jawaban dapat Anda memperjelas yang posting di atas Anda maksud untuk pembaca QA populer ini. Terima kasih.
Jeremy Thompson
1
@JeremyThompson Anda benar, terima kasih, saya akan mengedit posting. Saya akan menggunakan solusi dari Anda, terutama karena penggunaan Anda yang benar FileSharedan memeriksa kunci.
atlaste
2

Berikut adalah beberapa kode yang sejauh yang saya tahu terbaik dapat melakukan hal yang sama dengan jawaban yang diterima tetapi dengan kode lebih sedikit:

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

Namun saya pikir lebih kuat untuk melakukannya dengan cara berikut:

    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
sumber
2

Anda dapat menggunakan perpustakaan saya untuk mengakses file dari berbagai aplikasi.

Anda dapat menginstalnya dari nuget: Install-Package Xabe.FileLock

Jika Anda ingin informasi lebih lanjut tentang hal ini, periksa https://github.com/tomaszzmuda/Xabe.FileLock

ILock fileLock = new FileLock(file);
if(fileLock.Acquire(TimeSpan.FromSeconds(15), true))
{
    using(fileLock)
    {
        // file operations here
    }
}

Metode fileLock.Acquire akan mengembalikan true hanya jika dapat mengunci file eksklusif untuk objek ini. Tetapi aplikasi yang mengunggah file juga harus melakukannya dalam kunci file. Jika objek tidak dapat diakses, metod mengembalikan false.

Tomasz Żmuda
sumber
1
Tolong jangan hanya memposting beberapa alat atau perpustakaan sebagai jawaban. Setidaknya tunjukkan bagaimana cara memecahkan masalah dalam jawaban itu sendiri.
paper1111
Menambahkan demo :) Maaf @ paper1111
Tomasz Żmuda
1
Membutuhkan semua proses yang menggunakan file untuk bekerja sama. Tidak mungkin berlaku untuk masalah awal OP.
Harry Johnston
2

Saya pernah perlu mengunggah PDF ke arsip cadangan online. Tetapi cadangan akan gagal jika pengguna membuka file di program lain (seperti pembaca PDF). Dengan tergesa-gesa, saya mencoba beberapa jawaban teratas di utas ini tetapi tidak dapat membuatnya bekerja. Apa yang berhasil bagi saya adalah mencoba untuk memindahkan file PDF ke direktori sendiri . Saya menemukan bahwa ini akan gagal jika file dibuka di program lain, dan jika langkah itu berhasil tidak akan ada operasi pemulihan yang diperlukan karena akan ada jika dipindahkan ke direktori yang terpisah. Saya ingin memposting solusi dasar saya jika berguna untuk kasus penggunaan khusus orang lain.

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
sumber
0

Saya tertarik untuk melihat apakah ini memicu refleks WTF. Saya memiliki proses yang membuat dan kemudian meluncurkan dokumen PDF dari aplikasi konsol. Namun, saya berurusan dengan kelemahan di mana jika pengguna menjalankan proses beberapa kali, menghasilkan file yang sama tanpa terlebih dahulu menutup file yang dihasilkan sebelumnya, aplikasi akan melempar pengecualian dan mati. Ini adalah kejadian yang agak sering karena nama file didasarkan pada nomor kutipan penjualan.

Alih-alih gagal dengan cara yang tidak berterima, saya memutuskan untuk mengandalkan versi file yang ditambahkan secara otomatis:

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

Mungkin lebih banyak perhatian dapat diberikan pada catchblok untuk memastikan saya menangkap IOException yang benar. Saya mungkin juga akan menghapus penyimpanan aplikasi saat startup karena file-file ini dimaksudkan untuk sementara waktu.

Saya menyadari ini melampaui lingkup pertanyaan OP yaitu hanya memeriksa apakah file tersebut digunakan tetapi ini memang masalah yang saya cari untuk diselesaikan ketika saya tiba di sini jadi mungkin itu akan berguna untuk orang lain.

Vinney Kelly
sumber
0

Apakah sesuatu seperti ini membantu?

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
sumber
-2

Coba dan pindahkan / salin file ke direktori temp. Jika Anda bisa, itu tidak memiliki kunci dan Anda dapat bekerja dengan aman di direktori temp tanpa mendapatkan kunci. Jika tidak, cobalah untuk memindahkannya lagi dalam x detik.

Carra
sumber
@ jcolebrand mengunci apa? yang Anda salin? Atau yang Anda masukkan ke direktori temp?
Cullub
5
jika Anda menyalin file, berharap tidak ada orang lain yang akan mengerjakannya, dan Anda akan menggunakan file temp, dan kemudian seseorang menguncinya tepat setelah Anda menyalinnya, maka Anda berpotensi kehilangan data.
jcolebrand
-3

Saya menggunakan solusi ini, tetapi saya memiliki rentang waktu antara ketika saya memeriksa penguncian file dengan fungsi IsFileLocked dan ketika saya membuka file. Dalam rentang waktu ini beberapa utas lainnya dapat membuka file, jadi saya akan mendapatkan IOException.

Jadi, saya menambahkan kode tambahan untuk ini. Dalam kasus saya, saya ingin memuat 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!!!!!");
            }
        }

Bagaimana menurut anda? Bisakah saya mengubah sesuatu? Mungkin saya tidak harus menggunakan fungsi IsFileBeingUsed sama sekali?

Terima kasih

zzfima
sumber
4
apa IsFileBeingUsed? kode sumber tentang IsFileBeingUsed?
Kiquenet