Có cách nào để kiểm tra nếu một tập tin đang được sử dụng?


846

Tôi đang viết một chương trình bằng C # cần liên tục truy cập 1 tệp hình ảnh. Hầu hết thời gian nó hoạt động, nhưng nếu máy tính của tôi chạy nhanh, nó sẽ cố truy cập tệp trước khi nó được lưu lại vào hệ thống tệp và đưa ra lỗi: "Tệp được sử dụng bởi một quy trình khác" .

Tôi muốn tìm cách khắc phục điều này, nhưng tất cả Google của tôi chỉ mang lại việc tạo séc bằng cách sử dụng xử lý ngoại lệ. Điều này là trái với tôn giáo của tôi, vì vậy tôi đã tự hỏi nếu có ai có cách làm tốt hơn?


30
Được rồi, bạn có thể kiểm tra nó bằng cách kiểm tra tất cả các tay cầm mở trên hệ thống. Tuy nhiên, vì Windows là một hệ điều hành đa nhiệm, nên có thể ngay sau khi bạn chạy mã để xác định xem tệp có mở không và bạn cho rằng nó không phải, mã quy trình bắt đầu sử dụng tệp đó, sau đó bạn sẽ cố gắng sử dụng nó, bạn nhận được một lỗi. Nhưng, không có gì sai khi kiểm tra trước; đừng cho rằng nó không được sử dụng khi bạn thực sự cần nó.
BobbyShaftoe

3
Nhưng chỉ cho vấn đề cụ thể này; Tôi khuyên bạn không nên kiểm tra tay cầm tập tin và chỉ thử một số lần đặt trước, nói 3-5 trước khi thất bại.
BobbyShaftoe

Làm thế nào là tập tin hình ảnh này được tạo ra? Bạn có thể dừng / ngủ / tạm dừng chương trình của mình cho đến khi thế hệ hoàn thành? Đó là một cách vượt trội để xử lý tình huống. Nếu không, thì tôi không nghĩ bạn có thể tránh sử dụng xử lý ngoại lệ.
Catchwa

Không phải tất cả đều sử dụng ngoại lệ để kiểm tra một số giả định bằng cách làm điều gì đó có khả năng nguy hiểm trong khi cố tình không loại trừ khả năng thất bại?
JWG

26
Triết lý của bạn có một sự hiểu biết xấu về các ngoại lệ. Hầu hết mọi người nghĩ ngoại lệ có nghĩa là thánh-tào lao-hết-sai-gì-sai-chết-chết-chết. Khi ngoại lệ có nghĩa là .... ngoại lệ. Nó có nghĩa là một cái gì đó đặc biệt xảy ra mà bạn cần phải "xử lý" (hoặc tài khoản cho). Có thể bạn muốn tiếp tục thử lại để truy cập dữ liệu, có thể người dùng cần biết rằng bạn không thể có kết nối. Bạn làm nghề gì? Bạn xử lý ConnectionFailsException và thông báo cho người dùng, vì vậy có thể, họ sẽ ngừng thử sau một giờ và thông báo rằng cáp được rút ra.
Lee Louviere

Câu trả lời:


543

LƯU Ý đã cập nhật về giải pháp này : Việc kiểm tra với FileAccess.ReadWritesẽ thất bại đối với các tệp Chỉ đọc để giải pháp đã được sửa đổi để kiểm tra FileAccess.Read. Mặc dù giải pháp này hoạt động vì cố gắng kiểm tra FileAccess.Readsẽ thất bại nếu tệp có khóa Ghi hoặc Đọc trên đó, tuy nhiên, giải pháp này sẽ không hoạt động nếu tệp không có khóa Ghi hoặc Đọc trên đó, tức là nó đã được mở (để đọc hoặc viết) với quyền truy cập FileShare.Read hoặc FileShare.Write.

NGUỒN GỐC: Tôi đã sử dụng mã này trong nhiều năm qua và tôi không gặp vấn đề gì với nó.

Hiểu được sự do dự của bạn về việc sử dụng ngoại lệ, nhưng bạn không thể tránh chúng mọi lúc:

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
Đây là một giải pháp tuyệt vời, nhưng tôi có một nhận xét - bạn có thể không mở Tệp với chế độ truy cập FileAccess.Read vì ReadWrite sẽ luôn bị lỗi nếu tệp chỉ ở chế độ đọc.
adeel825

220
-1. Đây là một câu trả lời kém, vì tệp có thể bị khóa bởi một luồng / tiến trình khác sau khi nó bị đóng trong IsFileLocked và trước khi luồng của bạn có cơ hội mở nó.
Polyfun

16
Tôi nghĩ rằng đây là một câu trả lời tuyệt vời. Tôi đang sử dụng điều này như một phương pháp mở rộng á la public static bool IsLocked(this FileInfo file) {/*...*/}.
Manuzor

54
@ChrisW: bạn có thể tự hỏi chuyện gì đang xảy ra. Đừng hoảng sợ. Bạn đang phải chịu sự phẫn nộ của cộng đồng WTF hàng ngày: thed Dailywtf.com/Comments/ Khăn
Pierre Lebeaupin

16
@ChrisW Tại sao đó là một điều xấu. Cộng đồng này ở đây để chỉ ra câu trả lời tốt và xấu. Nếu một loạt các chuyên gia nhận thấy đây là một điều xấu, và tham gia để downvote, thì trang web là WAI. Và trước khi bạn nhận được tiêu cực, nếu bạn đọc bài viết đó, họ nói "hãy trả lời đúng" chứ không phải đánh giá sai. Bạn có muốn họ giải thích upvote của họ trong ý kiến ​​là tốt. Cảm ơn đã giới thiệu cho tôi một trang web tốt khác!
Lee Louviere

569

Bạn có thể bị một điều kiện chạy đua về vấn đề này, trong đó có các ví dụ được chứng minh về việc này đang được sử dụng như một lỗ hổng bảo mật. Nếu bạn kiểm tra xem tệp có sẵn không, nhưng sau đó thử và sử dụng nó, bạn có thể ném vào thời điểm đó, một người dùng độc hại có thể sử dụng để ép buộc và khai thác mã của bạn.

Đặt cược tốt nhất của bạn là thử bắt / cuối cùng cố gắng xử lý tệp.

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. Không có cách nào an toàn 100% để "tìm hiểu nếu một tệp đang được sử dụng" bởi vì mili giây sau khi bạn kiểm tra, tệp có thể không được sử dụng nữa hoặc ngược lại. Thay vào đó, bạn chỉ cần mở tệp và sử dụng nó nếu không có ngoại lệ.
Sedat Kapanoglu

8
Quá tệ .NET không hỗ trợ CAS. Một cái gì đó như, TryOpenFile (Ref FileHandle) trả về thành công / thất bại. Luôn luôn phải có một công việc xung quanh không chỉ dựa vào xử lý ngoại lệ. Tôi tự hỏi làm thế nào Microsoft Office làm điều đó.
TamusJRoyce

2
Điều quan trọng cần hiểu ở đây là API này chỉ đơn giản là sử dụng API windows để xử lý tệp. Vì vậy, họ cần dịch mã lỗi nhận được từ API C và bọc nó thành một ngoại lệ để ném. Chúng tôi có xử lý ngoại lệ trong .Net, tại sao không sử dụng nó. Bằng cách đó, bạn có thể viết một đường dẫn chuyển tiếp sạch trong mã của mình và để lại việc xử lý lỗi trong một đường dẫn mã riêng biệt.
Spence

36
Câu lệnh sử dụng là để đảm bảo luồng được đóng sau khi tôi hoàn thành. Tôi nghĩ bạn sẽ thấy rằng việc sử dụng () {} ít ký tự hơn thử {} cuối cùng {obj.Dispose ()}. Bạn cũng sẽ thấy rằng bây giờ bạn cần khai báo tham chiếu đối tượng của bạn bên ngoài câu lệnh bằng cách sử dụng nhiều kiểu gõ hơn. Nếu bạn có một giao diện rõ ràng, bạn cũng phải sử dụng. Cuối cùng, bạn muốn loại bỏ ASAP và logic cuối cùng có thể có UI hoặc bất kỳ hành động chạy dài nào khác ít liên quan đến việc gọi IDispose. </ rant>
Spence

2
điều đó không phủ nhận thực tế là bạn phải khai báo đối tượng của mình bên ngoài thử và phải gọi một cách rõ ràng là xử lý, điều này sử dụng cho bạn và có nghĩa là điều tương tự.
Spence

92

Sử dụng điều này để kiểm tra nếu một tập tin bị khóa:

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

Vì lý do hiệu suất, tôi khuyên bạn nên đọc nội dung tệp trong cùng một thao tác. Dưới đây là một số ví dụ:

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

Hãy tự mình thử:

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
Tôi sẽ nâng cấp nếu có quá nhiều "số ma thuật" trong đó en.wikipedia.org/wiki/Magic_number_(programming)
Kris

3
Tôi đã đề cập đến các so sánh errorCode, không phải là sự thay đổi bit. mặc dù bây giờ bạn đề cập đến nó ...
Kris

1
Catch của bạn nên được bật IOException, thay vì nói chung Exceptionvà sau đó là một bài kiểm tra về loại.
Askolein

3
@JeremyThndry buồn thay bạn đặt cụ thể IOExceptionsau cái chung. Người chung sẽ nắm bắt mọi thứ đi qua và cụ thể IOExceptionsẽ luôn cô đơn. Chỉ cần trao đổi hai.
Askolein

Tôi thích giải pháp này. Một đề nghị khác: bên trong cái bẫy như một cái khác với if (IsFileLocked (ex)) tôi sẽ ném ex. Điều này sau đó sẽ xử lý trường hợp tệp không tồn tại (hoặc bất kỳ IOException nào khác) bằng cách ném ngoại lệ.
shindigo

7

Chỉ cần sử dụng ngoại lệ như dự định. Chấp nhận rằng tệp đang được sử dụng và thử lại, lặp đi lặp lại cho đến khi hành động của bạn được hoàn thành. Đây cũng là cách hiệu quả nhất vì bạn không lãng phí bất kỳ chu kỳ nào kiểm tra trạng thái trước khi hành động.

Sử dụng chức năng dưới đây, ví dụ

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

Phương pháp tái sử dụng hết thời gian sau 2 giây

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

Có lẽ bạn có thể sử dụng FileSystemWatcher và theo dõi sự kiện Thay đổi.

Tôi đã không sử dụng điều này bản thân mình, nhưng nó có thể đáng giá một shot. Nếu bộ xử lý tập tin hóa ra hơi nặng đối với trường hợp này, tôi sẽ đi vòng lặp thử / bắt / ngủ.


1
Sử dụng FileSystemWatcher không giúp ích gì, vì các sự kiện được tạo và thay đổi sẽ tăng khi bắt đầu tạo / thay đổi tệp. Ngay cả các tệp nhỏ cũng cần nhiều thời gian hơn để được viết và đóng bởi hệ điều hành so với ứng dụng .NET cần chạy qua CallS của FileSystemEventHandler. Điều này thật đáng buồn, nhưng không có lựa chọn nào khác ngoài việc ước tính thời gian chờ đợi trước khi truy cập tệp hoặc chạy vào các vòng lặp ngoại lệ ...

Mặc dù vậy, FileSystemWatcher không xử lý tốt nhiều thay đổi cùng một lúc, vì vậy hãy cẩn thận với điều đó.
Ben F

1
BTW, các bạn có để ý trong khi gỡ lỗi và xem các luồng mà MS gọi là FSW của riêng họ "FileSystemWather" không? Thế nào là một kẻ lang thang?
devlord

Chính xác tôi đã gặp vấn đề này bởi vì một dịch vụ windows với FileSystemWatcher cố gắng đọc tệp trước khi quá trình đóng nó.
triển tự do

6

Bạn có thể trả về một tác vụ cung cấp cho bạn một luồng ngay khi nó có sẵn. Đó là một giải pháp đơn giản hóa, nhưng nó là một điểm khởi đầu tốt. Đó là chủ đề an toàn.

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

Bạn có thể sử dụng luồng này như bình thường:

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

3
Bao nhiêu giây cho đến khi một ngăn xếp tràn từ đệ quy trong GetStreamAsync()?
CAD bloke

@CADbloke, bạn đã nêu lên một điểm rất tốt. Thật vậy, mẫu của tôi có thể có ngoại lệ tràn ngăn xếp, trong trường hợp nếu một tệp không có sẵn trong một thời gian dài. Liên quan đến câu trả lời này stackoverflow.com/questions/4513438/ , nó có thể tăng ngoại lệ trong 5 giờ.
Ivan Branets

Liên quan đến các trường hợp sử dụng của bạn, tốt nhất là ném ngoại lệ I / O nếu giả sử 10 lần đọc tệp đã thất bại. Chiến lược khác có thể là tăng thời gian chờ đợi trong một giây khi 10 lần thử đã thất bại. Bạn cũng có thể sử dụng kết hợp cả hai.
Ivan Branets

Tôi sẽ (và làm) chỉ đơn giản là cảnh báo cho người dùng rằng tập tin bị khóa. Họ thường tự khóa nó vì vậy họ có thể sẽ làm gì đó với nó. Hay không.
CAD bloke

Trong một số trường hợp, bạn cần sử dụng chính sách thử lại vì một tệp có thể chưa sẵn sàng. Hãy tưởng tượng một ứng dụng máy tính để bàn để tải hình ảnh trong một số thư mục tạm thời. Ứng dụng bắt đầu tải xuống và đồng thời bạn mở thư mục này trong trình duyệt tệp. Windows muốn tạo một hình thu nhỏ ngay lập tức và khóa tệp. Đồng thời, ứng dụng của bạn cố gắng thay thế hình ảnh bị khóa sang một nơi khác. Bạn sẽ nhận được một ngoại lệ nếu bạn không sử dụng chính sách thử lại.
Ivan Branets

4

cách duy nhất tôi biết là sử dụng API khóa độc quyền Win32 không quá nhanh, nhưng vẫn tồn tại các ví dụ.

Hầu hết mọi người, cho một giải pháp đơn giản cho việc này, chỉ đơn giản là thử / bắt / ngủ vòng.


1
Bạn không thể sử dụng API này mà không cần mở tệp trước, tại thời điểm đó bạn không còn cần nữa.
Harry Johnston

1
Tập tin của 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");

Hi vọng điêu nay co ich!


10
Kiểm tra thực tế bạn thực hiện là tốt; đặt nó bên trong một chức năng là sai lệch. Bạn KHÔNG muốn sử dụng một chức năng như thế này trước khi mở tệp. Bên trong chức năng, tập tin được mở, kiểm tra và đóng. Sau đó, lập trình viên ĐÁNH GIÁ tệp vẫn VẪN sử dụng và thử mở nó để sử dụng. Điều này là xấu vì nó có thể được sử dụng và khóa bởi một quy trình khác được xếp hàng để mở tệp này. Giữa lần đầu tiên được mở (để kiểm tra) và lần thứ hai được mở (để sử dụng), HĐH có thể đã bỏ qua quy trình của bạn và có thể chạy một quy trình khác.
Lakey

3

Các câu trả lời được chấp nhận ở trên gặp phải một vấn đề trong đó nếu tệp đã được mở để ghi với chế độ FileShare.Read hoặc nếu tệp có thuộc tính Chỉ đọc thì mã sẽ không hoạt động. Giải pháp sửa đổi này hoạt động đáng tin cậy nhất, với hai điều cần lưu ý (cũng đúng với giải pháp được chấp nhận):

  1. Nó sẽ không hoạt động đối với các tệp đã được mở với chế độ chia sẻ ghi
  2. Điều này không tính đến các vấn đề luồng, vì vậy bạn sẽ cần phải khóa nó hoặc xử lý các vấn đề luồng riêng biệt.

Hãy ghi nhớ những điều trên, việc này sẽ kiểm tra xem tệp có bị khóa để ghi hoặc khóa để tránh đọc không :

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

Vẫn có cùng một vấn đề như câu trả lời được chấp nhận - nó chỉ cho bạn biết liệu tệp có bị khóa bởi một quy trình khác tại một thời điểm cụ thể không , đó không phải là thông tin hữu ích. Vào thời điểm chức năng đã trả về, kết quả có thể đã hết hạn!
Harry Johnston

1
Điều đó đúng, người ta chỉ có thể kiểm tra tại bất kỳ thời điểm nào (hoặc đăng ký các sự kiện), ưu điểm của phương pháp này so với giải pháp được chấp nhận là nó có thể kiểm tra thuộc tính chỉ đọc và khóa ghi và không trả về kết quả dương tính giả.
rboy

3

Ngoài việc làm việc 3 lớp và chỉ để tham khảo: Nếu bạn muốn thông tin đầy đủ - có một dự án nhỏ trên Microsoft Dev Center:

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

Từ phần giới thiệu:

Mã mẫu C # được phát triển trong .NET Framework 4.0 sẽ giúp tìm ra quy trình nào có khóa trên tệp. Hàm RmStartSession được bao gồm trong rstrtmgr.dll đã được sử dụng để tạo phiên quản lý khởi động lại và theo kết quả trả về, một phiên bản mới của đối tượng Win32Exception được tạo. Sau khi đăng ký tài nguyên cho phiên Khởi động lại Trình quản lý thông qua chức năng RmRegisterRescource , chức năng RmGetList được gọi để kiểm tra các ứng dụng đang sử dụng một tệp cụ thể bằng cách liệt kê mảng RM_PROCESS_INFO .

Nó hoạt động bằng cách kết nối với "Phiên khởi động lại trình quản lý".

Trình quản lý khởi động lại sử dụng danh sách các tài nguyên đã đăng ký với phiên để xác định ứng dụng và dịch vụ nào phải được tắt và khởi động lại. Tài nguyên có thể được xác định bằng tên tệp, tên dịch vụ ngắn hoặc cấu trúc RM_UNIQUE_PROCESS mô tả các ứng dụng đang chạy.

Nó có thể là một chút overengineered cho các nhu cầu cụ thể của bạn ... Nhưng nếu đó là những gì bạn muốn, đi trước và lấy vs dự án.


2

Theo kinh nghiệm của tôi, bạn thường muốn làm điều này, sau đó 'bảo vệ' các tệp của bạn để làm một cái gì đó lạ mắt và sau đó sử dụng các tệp 'được bảo vệ'. Nếu bạn chỉ có một tệp bạn muốn sử dụng như thế này, bạn có thể sử dụng mẹo được giải thích trong câu trả lời của Jeremy Thompson. Tuy nhiên, nếu bạn cố gắng thực hiện việc này trên nhiều tệp (ví dụ: khi bạn đang viết trình cài đặt), bạn sẽ gặp khá nhiều tổn thương.

Một cách rất hay mà điều này có thể được giải quyết là sử dụng thực tế là hệ thống tệp của bạn sẽ không cho phép bạn thay đổi tên thư mục nếu một trong các tệp ở đó được sử dụng. Giữ thư mục trong cùng một hệ thống tệp và nó sẽ hoạt động như một lá bùa.

Hãy lưu ý rằng bạn nên biết về những cách rõ ràng mà điều này có thể được khai thác. Rốt cuộc, các tập tin sẽ không bị khóa. Ngoài ra, hãy lưu ý rằng có những lý do khác có thể khiến Movehoạt động của bạn bị lỗi. Rõ ràng việc xử lý lỗi thích hợp (MSDN) có thể giúp đỡ ở đây.

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

Đối với các tệp riêng lẻ, tôi sẽ sử dụng đề xuất khóa được đăng bởi Jeremy Thompson.


Xin chào, Vì thứ tự câu trả lời thay đổi, bạn có thể làm rõ bài đăng nào ở trên đối với độc giả của QA phổ biến này. Cảm ơn.
Jeremy Thompson

1
@JeremyThndry Bạn nói đúng, cảm ơn, tôi sẽ chỉnh sửa bài viết. Tôi sẽ sử dụng giải pháp từ bạn, chủ yếu là do bạn sử dụng đúng FileSharevà kiểm tra khóa.
đúng

2

Đây là một số mã mà theo như tôi có thể nói tốt nhất làm điều tương tự như câu trả lời được chấp nhận nhưng với ít mã hơn:

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

Tuy nhiên tôi nghĩ sẽ mạnh mẽ hơn khi làm theo cách sau:

    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

Bạn có thể sử dụng thư viện của tôi để truy cập các tệp từ nhiều ứng dụng.

Bạn có thể cài đặt nó từ nuget: Cài đặt-Gói Xabe.FileLock

Nếu bạn muốn biết thêm thông tin về nó, hãy kiểm tra https://github.com/tomaszzmuda/Xabe.FileLock

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

Phương thức fileLock.Acquire sẽ trả về true chỉ khi có thể khóa tệp độc quyền cho đối tượng này. Nhưng ứng dụng tải lên tập tin cũng phải làm điều đó trong khóa tập tin. Nếu đối tượng không thể truy cập metod trả về false.


1
Xin đừng chỉ đăng một số công cụ hoặc thư viện như một câu trả lời. Ít nhất là chứng minh làm thế nào nó giải quyết vấn đề trong chính câu trả lời.
giấy1111

Đã thêm bản demo :) Xin lỗi @ paper1111
Tomasz muda

1
Yêu cầu tất cả các quy trình đang sử dụng tệp để hợp tác. Không có khả năng áp dụng cho vấn đề ban đầu của OP.
Harry Johnston

2

Tôi đã từng cần phải tải tệp PDF lên một kho lưu trữ sao lưu trực tuyến. Nhưng sao lưu sẽ thất bại nếu người dùng mở tệp trong một chương trình khác (chẳng hạn như trình đọc PDF). Trong sự vội vàng của mình, tôi đã thử một vài câu trả lời hàng đầu trong chủ đề này nhưng không thể khiến chúng hoạt động được. Những gì đã làm cho tôi là cố gắng di chuyển tệp PDF vào thư mục riêng của nó . Tôi thấy rằng điều này sẽ thất bại nếu tệp được mở trong một chương trình khác và nếu việc di chuyển thành công thì sẽ không có thao tác khôi phục cần thiết như sẽ có nếu nó được chuyển sang một thư mục riêng. Tôi muốn đăng giải pháp cơ bản của mình trong trường hợp nó có thể hữu ích cho các trường hợp sử dụng cụ thể của người khác.

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

Tôi quan tâm xem liệu điều này có kích hoạt bất kỳ phản xạ WTF nào không. Tôi có một quy trình tạo và sau đó khởi chạy một tài liệu PDF từ ứng dụng bảng điều khiển. Tuy nhiên, tôi đã xử lý một điểm yếu trong đó nếu người dùng chạy quy trình nhiều lần, tạo cùng một tệp mà không đóng tệp được tạo trước đó, ứng dụng sẽ ném ngoại lệ và chết. Đây là một sự xuất hiện khá thường xuyên vì tên tệp dựa trên số báo giá bán hàng.

Thay vì thất bại theo cách vô duyên như vậy, tôi quyết định dựa vào phiên bản tệp tăng tự động:

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

Có lẽ một số chăm sóc khác có thể được cung cấp cho catchkhối để đảm bảo tôi bắt được IOException (s) chính xác. Có lẽ tôi cũng sẽ xóa bộ nhớ ứng dụng khi khởi động vì các tệp này được dự định là tạm thời.

Tôi nhận ra điều này vượt ra ngoài phạm vi câu hỏi của OP chỉ đơn giản là kiểm tra xem tệp có được sử dụng hay không nhưng đây thực sự là vấn đề tôi đang tìm cách giải quyết khi đến đây nên có lẽ nó sẽ hữu ích cho người khác.


0

Một cái gì đó như thế này sẽ giúp?

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

Hãy thử và di chuyển / sao chép tệp vào một thư mục tạm thời. Nếu bạn có thể, nó không có khóa và bạn có thể làm việc một cách an toàn trong thư mục tạm thời mà không cần khóa. Khác chỉ cần cố gắng di chuyển nó một lần nữa trong x giây.


@jcolebrand khóa cái gì? một trong những bạn đã sao chép? Hoặc một trong những bạn đặt trong thư mục tạm thời?
Cullub

5
nếu bạn sao chép tệp, hy vọng không có ai khác làm việc với nó và bạn sẽ sử dụng tệp tạm thời và sau đó ai đó sẽ khóa tệp ngay sau khi bạn sao chép tệp, thì bạn có khả năng bị mất dữ liệu.
jcolebrand

-3

Tôi sử dụng cách giải quyết này, nhưng tôi có khoảng thời gian giữa khi tôi kiểm tra khóa tệp bằng chức năng IsFileLocked và khi tôi mở tệp. Trong thời gian này, một số luồng khác có thể mở tệp, vì vậy tôi sẽ nhận được IOException.

Vì vậy, tôi đã thêm mã bổ sung cho việc này. Trong trường hợp của tôi, tôi muốn tải 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!!!!!");
            }
        }

Bạn nghĩ sao? Tôi có thể thay đổi một số thứ? Có lẽ tôi không phải sử dụng chức năng IsFileByingUsed?

Cảm ơn


4
IsFileByingUsed là gì? mã nguồn về IsFileByingUsed?
Kiquenet
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.