且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

IOException:该进程无法访问文件“文件路径",因为它正被另一个进程使用

更新时间:2023-01-31 16:37:00

是什么原因?

错误消息非常清楚:您正在尝试访问一个文件,但由于另一个进程(甚至是同一个进程)正在处理它(并且它不允许任何共享),所以它无法访问.

What is the cause?

The error message is pretty clear: you're trying to access a file, and it's not accessible because another process (or even the same process) is doing something with it (and it didn't allow any sharing).

这可能很容易解决(或很难理解),具体取决于您的具体情况.来看看吧.

It may be pretty easy to solve (or pretty hard to understand), depending on your specific scenario. Let's see some.

您的进程是唯一可以访问该文件的进程
您确定 other 进程是您自己的进程.如果您知道您在程序的另一部分中打开了该文件,那么首先您必须在每次使用后检查您是否正确关闭了文件句柄.这是一个带有此错误的代码示例:

Your process is the only one to access that file
You're sure the other process is your own process. If you know you open that file in another part of your program, then first of all you have to check that you properly close the file handle after each use. Here is an example of code with this bug:

var stream = new FileStream(path, FileAccess.Read);
var reader = new StreamReader(stream);
// Read data from this file, when I'm done I don't need it any more
File.Delete(path); // IOException: file is in use

幸运的是 FileStream 实现了 IDisposable,因此很容易将所有代码包装在 using 语句中:

Fortunately FileStream implements IDisposable, so it's easy to wrap all your code inside a using statement:

using (var stream = File.Open("myfile.txt", FileMode.Open)) {
    // Use stream
}

// Here stream is not accessible and it has been closed (also if
// an exception is thrown and stack unrolled

此模式还将确保文件不会在异常情况下保持打开状态(这可能是文件正在使用的原因:出了点问题,没有人关闭它;参见 这篇文章例如).

This pattern will also ensure that the file won't be left open in case of exceptions (it may be the reason the file is in use: something went wrong, and no one closed it; see this post for an example).

如果一切正常(您确定总是关闭打开的每个文件,即使出现异常情况也是如此)并且您有多个工作线程,那么您有两个选择:重新编写代码以序列化文件访问(并非总是可行并不总是需要)或应用重试模式.这是 I/O 操作的一种非常常见的模式:您尝试做某事并在出现错误时等待并重试(您是否问过自己为什么,例如,Windows Shell 需要一些时间来通知您文件正在使用中并且不能删除?).在 C# 中它很容易实现(另请参阅关于 磁盘 I/O网络数据库访问).

If everything seems fine (you're sure you always close every file you open, even in case of exceptions) and you have multiple working threads, then you have two options: rework your code to serialize file access (not always doable and not always wanted) or apply a retry pattern. It's a pretty common pattern for I/O operations: you try to do something and in case of error you wait and try again (did you ask yourself why, for example, Windows Shell takes some time to inform you that a file is in use and cannot be deleted?). In C# it's pretty easy to implement (see also better examples about disk I/O, networking and database access).

private const int NumberOfRetries = 3;
private const int DelayOnRetry = 1000;

for (int i=1; i <= NumberOfRetries; ++i) {
    try {
        // Do stuff with file
        break; // When done we can break loop
    }
    catch (IOException e) when (i <= NumberOfRetries) {
        // You may check error code to filter some exceptions, not every error
        // can be recovered.
        Thread.Sleep(DelayOnRetry);
    }
}

请注意我们在 *** 上经常看到的一个常见错误:

Please note a common error we see very often on ***:

var stream = File.Open(path, FileOpen.Read);
var content = File.ReadAllText(path);

在这种情况下,ReadAllText() 将失败,因为文件正在使用中(File.Open() 在前面的行中).事先打开文件不仅没有必要,而且是错误的.这同样适用于所有不向您正在使用的文件返回 句柄File 函数:File.ReadAllText(),File.WriteAllText()File.ReadAllLines()File.WriteAllLines() 等(如 File.AppendAllXyz() 函数)都将自行打开和关闭文件.

In this case ReadAllText() will fail because the file is in use (File.Open() in the line before). To open the file beforehand is not only unnecessary but also wrong. The same applies to all File functions that don't return a handle to the file you're working with: File.ReadAllText(), File.WriteAllText(), File.ReadAllLines(), File.WriteAllLines() and others (like File.AppendAllXyz() functions) will all open and close the file by themselves.

您的进程不是唯一访问该文件的进程
如果您的进程不是唯一访问该文件的进程,那么交互可能会更加困难.重试模式会有所帮助(如果文件不应该被其他人打开,但它是,那么您需要像 Process Explorer 这样的实用程序来检查 在做 什么).

Your process is not the only one to access that file
If your process is not the only one to access that file, then interaction can be harder. A retry pattern will help (if the file shouldn't be open by anyone else but it is, then you need a utility like Process Explorer to check who is doing what).

如果适用,请始终使用 using 语句打开文件.如上一段所述,它将积极帮助您避免许多常见错误(请参阅 这篇文章以获取关于 如何不使用它).

When applicable, always use using statements to open files. As said in previous paragraph, it'll actively help you to avoid many common errors (see this post for an example on how not to use it).

如果可能,请尝试确定谁拥有对特定文件的访问权限,并通过一些众所周知的方法集中访问权限.例如,如果您有一个数据文件供您的程序读写,那么您应该将所有 I/O 代码封装在一个类中.它将使调试更容易(因为您可以随时在此处放置断点并查看谁在做什么),并且它还将成为多个访问的同步点(如果需要).

If possible, try to decide who owns access to a specific file and centralize access through a few well-known methods. If, for example, you have a data file where your program reads and writes, then you should box all I/O code inside a single class. It'll make debug easier (because you can always put a breakpoint there and see who is doing what) and also it'll be a synchronization point (if required) for multiple access.

不要忘记 I/O 操作总是会失败,一个常见的例子是:

Don't forget I/O operations can always fail, a common example is this:

if (File.Exists(path))
    File.Delete(path);

如果有人File.Exists()之后File.Delete()之前删除了文件,那么它会抛出一个IOException 放在你可能错误地感到安全的地方.

If someone deletes the file after File.Exists() but before File.Delete(), then it'll throw an IOException in a place where you may wrongly feel safe.

只要有可能,应用重试模式,如果您使用的是 FileSystemWatcher,请考虑推迟操作(因为您会收到通知,但应用程序可能仍只使用该文件).

Whenever it's possible, apply a retry pattern, and if you're using FileSystemWatcher, consider postponing action (because you'll get notified, but an application may still be working exclusively with that file).

高级场景
这并不总是那么容易,因此您可能需要与其他人共享访问权限.例如,如果您是从头读到尾,那么您至少有两个选择.

Advanced scenarios
It's not always so easy, so you may need to share access with someone else. If, for example, you're reading from the beginning and writing to the end, you have at least two options.

1) 与适当的同步函数共享相同的 FileStream(因为 它不是线程安全的).请参阅 a> 以帖子为例.

1) share the same FileStream with proper synchronization functions (because it is not thread-safe). See this and this posts for an example.

2) 使用 FileShare 枚举来指示操作系统允许其他进程(或您自己进程的其他部分)同时访问同一文件.

2) use FileShare enumeration to instruct OS to allow other processes (or other parts of your own process) to access same file concurrently.

using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.Read))
{
}

在这个例子中,我展示了如何打开一个文件进行写入和共享读取;请注意,当读写重叠时,会导致未定义或无效数据.这是阅读时必须处理的情况.另请注意,这不会使访问 stream 线程安全,因此该对象不能与多个线程共享,除非访问以某种方式同步(请参阅前面的链接).其他共享选项可用,它们开辟了更复杂的场景.请参考 MSDN了解更多详情.

In this example I showed how to open a file for writing and share for reading; please note that when reading and writing overlaps, it results in undefined or invalid data. It's a situation that must be handled when reading. Also note that this doesn't make access to the stream thread-safe, so this object can't be shared with multiple threads unless access is synchronized somehow (see previous links). Other sharing options are available, and they open up more complex scenarios. Please refer to MSDN for more details.

一般 N 个进程可以一起从同一个文件中读取,但只有一个应该写入,在受控场景中,您甚至可以启用并发写入,但这不能在此内部的几个文本段落中概括回答.

In general N processes can read from same file all together but only one should write, in a controlled scenario you may even enable concurrent writings but this can't be generalized in few text paragraphs inside this answer.

是否可以解锁另一个进程使用的文件?这并不总是安全的,也不是那么容易,但是是的,有可能.

Is it possible to unlock a file used by another process? It's not always safe and not so easy but yes, it's possible.