【发布时间】:2021-12-02 17:25:36
【问题描述】:
我有一些代码,当它执行时,它会抛出一个IOException,表示
进程无法访问文件“文件名”,因为它正被 另一个进程
这是什么意思,我能做些什么?
【问题讨论】:
-
查看this question了解如何找到正在使用该文件的其他进程。
标签: c# .net language-agnostic ioexception
我有一些代码,当它执行时,它会抛出一个IOException,表示
进程无法访问文件“文件名”,因为它正被 另一个进程
这是什么意思,我能做些什么?
【问题讨论】:
标签: c# .net language-agnostic ioexception
错误消息非常清楚:您正在尝试访问一个文件,但由于另一个进程(甚至是同一个进程)正在处理它(并且它不允许任何共享),所以它无法访问。
这可能很容易解决(或很难理解),具体取决于您的具体情况。来看看吧。
您的进程是唯一可以访问该文件的进程
您确定 other 进程是您自己的进程。如果您知道您在程序的另一部分中打开了该文件,那么首先您必须在每次使用后检查您是否正确关闭了文件句柄。这是一个带有此错误的代码示例:
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 语句中:
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 post 示例)。
如果一切正常(您确定总是关闭打开的每个文件,即使出现异常情况也是如此)并且您有多个工作线程,那么您有两个选择:重新编写代码以序列化文件访问(并非总是可行并不总是需要)或应用重试模式。这是 I/O 操作的一种非常常见的模式:您尝试做某事并在出现错误时等待并重试(您是否问过自己为什么,例如,Windows Shell 需要一些时间来通知您文件正在使用中并且不能删除?)。在 C# 中它很容易实现(另请参阅关于 disk I/O、networking 和 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);
}
}
请注意我们在 StackOverflow 上经常看到的一个常见错误:
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() 函数)都会自己打开和关闭文件。
您的进程不是唯一访问该文件的进程
如果您的进程不是唯一访问该文件的进程,那么交互可能会更加困难。 重试模式会有所帮助(如果文件不应该被其他人打开,但它是,那么您需要像 Process Explorer 这样的实用程序来检查 谁 在做 什么)。
如果适用,请始终使用 using 语句打开文件。如上一段所述,它将积极帮助您避免许多常见错误(请参阅this post,了解有关如何不使用它的示例)。
如果可能,请尝试确定谁拥有对特定文件的访问权限,并通过一些众所周知的方法集中访问权限。例如,如果您有一个数据文件供您的程序读写,那么您应该将所有 I/O 代码封装在一个类中。它将使调试更容易(因为您总是可以在那里放置一个断点并查看谁在做什么),并且它还将成为多个访问的同步点(如果需要)。
不要忘记 I/O 操作总是会失败,一个常见的例子是:
if (File.Exists(path))
File.Delete(path);
如果某人在File.Exists()之后但File.Delete()之前删除了文件,那么它会在你可能错误地感到安全的地方抛出一个IOException。
尽可能应用重试模式,如果您使用的是FileSystemWatcher,请考虑推迟操作(因为您会收到通知,但应用程序可能仍专门使用该模式)文件)。
高级场景
这并不总是那么容易,因此您可能需要与其他人共享访问权限。例如,如果您是从头读到尾,那么您至少有两个选择。
1) 使用适当的同步函数共享相同的FileStream(因为它不是线程安全的)。有关示例,请参阅 this 和 this 帖子。
2) 使用FileShare 枚举来指示操作系统允许其他进程(或您自己进程的其他部分)同时访问同一文件。
using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.Read))
{
}
在这个例子中,我展示了如何打开一个文件进行写入和共享读取;请注意,当读写重叠时,会导致未定义或无效数据。这是阅读时必须处理的情况。另请注意,这不会使访问stream 线程安全,因此该对象不能与多个线程共享,除非以某种方式同步访问(请参阅前面的链接)。其他共享选项可用,它们开辟了更复杂的场景。详情请参考MSDN。
一般 N 个进程可以一起从同一个文件中读取,但只有一个应该写入,在受控场景中,您甚至可以启用并发写入,但这不能在此内部的几个文本段落中概括回答。
是否可以解锁另一个进程使用的文件?这并不总是安全的,也不是那么容易,但是是的,it's possible。
【讨论】:
File.Create(path),你应该在写文件之前添加.Close()。除了using 用于写入文件的语句之外,还有类似的陷阱,然后在它们之后删除。您应该在问题中发布有关如何创建和删除文件的代码。但可能符合上述内容。
Directory.SetCreationTimeUTC(),但是当文件资源管理器打开时它失败,声称该目录正在被另一个进程访问。这种情况应该怎么处理?
使用 FileShare 解决了我打开文件的问题,即使它是由另一个进程打开的。
using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
{
}
【讨论】:
上传图片时出现问题,无法删除,找到了解决方案。高音
//C# .NET
var image = Image.FromFile(filePath);
image.Dispose(); // this removes all resources
//later...
File.Delete(filePath); //now works
【讨论】:
问题
有人想用这种方法打开文件System.IO.File.Open(path, FileMode),并希望对文件进行共享访问,但是
如果您阅读了 System.IO.File.Open(path, FileMode) 的文档,则明确表示它不允许共享
解决方案
使用你必须使用 FileShare 的其他覆盖
using FileStream fs = System.IO.File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
FileShare.Read
【讨论】:
我收到此错误是因为我正在执行 File.Move 到没有文件名的文件路径,需要在目标中指定完整路径。
【讨论】:
正如该线程中的其他答案所指出的,要解决此错误,您需要仔细检查代码,以了解文件被锁定的位置。
在我的例子中,我在执行移动操作之前将文件作为电子邮件附件发送出去。
所以文件被锁定了几秒钟,直到 SMTP 客户端完成发送电子邮件。
我采用的解决方案是先移动文件,然后发送电子邮件。这解决了我的问题。
正如 Hudson 之前指出的,另一种可能的解决方案是在使用后丢弃该对象。
public static SendEmail()
{
MailMessage mMailMessage = new MailMessage();
//setup other email stuff
if (File.Exists(attachmentPath))
{
Attachment attachment = new Attachment(attachmentPath);
mMailMessage.Attachments.Add(attachment);
attachment.Dispose(); //disposing the Attachment object
}
}
【讨论】:
File.Move() 将不起作用并给出相同的错误。如果只是将文件添加到电子邮件中,我认为在 Attachments.Add() 操作期间使用时不会出错,因为这只是一个复制操作。如果由于某种原因确实如此,您可以将其复制到临时目录,附加副本,然后删除复制的文件。但我不认为,如果 OP 想要修改一个文件并使用它,这种解决方案(你没有显示代码,只有附件部分)会起作用。 .Dispose() 总是一个好主意,但在这里不相关,除非文件是在先前的操作中打开的。
该错误表明另一个进程正在尝试访问该文件。也许您或其他人在您尝试写入时将其打开。 “读取”或“复制”通常不会导致这种情况,但写入或调用删除会导致这种情况。
正如其他答案所提到的,有一些基本的事情可以避免这种情况:
在FileStream 操作中,将其放在using 块中,并使用FileShare.ReadWrite 访问模式。
例如:
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
{
}
请注意,如果您使用FileMode.Append,则无法使用FileAccess.ReadWrite。
当我在使用文件时使用输入流执行File.SaveAs 时遇到了这个问题。在我发现的情况下,我实际上根本不需要将它保存回文件系统,所以我最终只是删除了它,但我可能已经尝试使用 @987654330 在 using 语句中创建 FileStream @,很像上面的代码。
将您的数据另存为其他文件,并在发现不再使用时返回删除旧文件,然后将成功保存的文件重命名为原始文件的名称是一种选择。您如何测试正在使用的文件是通过
List<Process> lstProcs = ProcessHandler.WhoIsLocking(file);
下面我的代码中的行,并且可以在 Windows 服务中循环完成,如果你有一个特定的文件要在你想要替换它时定期查看和删除。如果您不总是拥有相同的文件,则可以更新文本文件或数据库表,该服务始终检查文件名,然后执行该检查进程并随后对其执行进程终止和删除,正如我所描述的在下一个选项中。请注意,您当然需要在给定计算机上具有管理员权限的帐户用户名和密码才能执行进程的删除和结束。
如果您在尝试保存文件时不知道文件是否正在使用,您可以在保存之前关闭所有可能正在使用它的进程,例如 Word,如果它是 Word 文档。
如果是本地的,你可以这样做:
ProcessHandler.localProcessKill("winword.exe");
如果是远程的,你可以这样做:
ProcessHandler.remoteProcessKill(computerName, txtUserName, txtPassword, "winword.exe");
其中txtUserName 的格式为DOMAIN\user。
假设您不知道锁定文件的进程名称。然后,您可以这样做:
List<Process> lstProcs = new List<Process>();
lstProcs = ProcessHandler.WhoIsLocking(file);
foreach (Process p in lstProcs)
{
if (p.MachineName == ".")
ProcessHandler.localProcessKill(p.ProcessName);
else
ProcessHandler.remoteProcessKill(p.MachineName, txtUserName, txtPassword, p.ProcessName);
}
请注意,file 必须是 UNC 路径:\\computer\share\yourdoc.docx,以便Process 确定它在哪台计算机上,p.MachineName 才有效。
下面是这些函数使用的类,需要添加对System.Management的引用。代码是originally written by Eric J.:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Management;
namespace MyProject
{
public static class ProcessHandler
{
[StructLayout(LayoutKind.Sequential)]
struct RM_UNIQUE_PROCESS
{
public int dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
const int RmRebootReasonNone = 0;
const int CCH_RM_MAX_APP_NAME = 255;
const int CCH_RM_MAX_SVC_NAME = 63;
enum RM_APP_TYPE
{
RmUnknownApp = 0,
RmMainWindow = 1,
RmOtherWindow = 2,
RmService = 3,
RmExplorer = 4,
RmConsole = 5,
RmCritical = 1000
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
public string strServiceShortName;
public RM_APP_TYPE ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)]
public bool bRestartable;
}
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
static extern int RmRegisterResources(uint pSessionHandle,
UInt32 nFiles,
string[] rgsFilenames,
UInt32 nApplications,
[In] RM_UNIQUE_PROCESS[] rgApplications,
UInt32 nServices,
string[] rgsServiceNames);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
[DllImport("rstrtmgr.dll")]
static extern int RmEndSession(uint pSessionHandle);
[DllImport("rstrtmgr.dll")]
static extern int RmGetList(uint dwSessionHandle,
out uint pnProcInfoNeeded,
ref uint pnProcInfo,
[In, Out] RM_PROCESS_INFO[] rgAffectedApps,
ref uint lpdwRebootReasons);
/// <summary>
/// Find out what process(es) have a lock on the specified file.
/// </summary>
/// <param name="path">Path of the file.</param>
/// <returns>Processes locking the file</returns>
/// <remarks>See also:
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
/// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
///
/// </remarks>
static public List<Process> WhoIsLocking(string path)
{
uint handle;
string key = Guid.NewGuid().ToString();
List<Process> processes = new List<Process>();
int res = RmStartSession(out handle, 0, key);
if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker.");
try
{
const int ERROR_MORE_DATA = 234;
uint pnProcInfoNeeded = 0,
pnProcInfo = 0,
lpdwRebootReasons = RmRebootReasonNone;
string[] resources = new string[] { path }; // Just checking on one resource.
res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
if (res != 0) throw new Exception("Could not register resource.");
//Note: there's a race condition here -- the first call to RmGetList() returns
// the total number of process. However, when we call RmGetList() again to get
// the actual processes this number may have increased.
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
if (res == ERROR_MORE_DATA)
{
// Create an array to store the process results
RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
pnProcInfo = pnProcInfoNeeded;
// Get the list
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
if (res == 0)
{
processes = new List<Process>((int)pnProcInfo);
// Enumerate all of the results and add them to the
// list to be returned
for (int i = 0; i < pnProcInfo; i++)
{
try
{
processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
}
// catch the error -- in case the process is no longer running
catch (ArgumentException) { }
}
}
else throw new Exception("Could not list processes locking resource.");
}
else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result.");
}
finally
{
RmEndSession(handle);
}
return processes;
}
public static void remoteProcessKill(string computerName, string userName, string pword, string processName)
{
var connectoptions = new ConnectionOptions();
connectoptions.Username = userName;
connectoptions.Password = pword;
ManagementScope scope = new ManagementScope(@"\\" + computerName + @"\root\cimv2", connectoptions);
// WMI query
var query = new SelectQuery("select * from Win32_process where name = '" + processName + "'");
using (var searcher = new ManagementObjectSearcher(scope, query))
{
foreach (ManagementObject process in searcher.Get())
{
process.InvokeMethod("Terminate", null);
process.Dispose();
}
}
}
public static void localProcessKill(string processName)
{
foreach (Process p in Process.GetProcessesByName(processName))
{
p.Kill();
}
}
[DllImport("kernel32.dll")]
public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);
public const int MOVEFILE_DELAY_UNTIL_REBOOT = 0x4;
}
}
【讨论】:
我遇到了导致相同错误的以下情况:
大多数文件都很小,但也有一些很大,因此尝试删除这些文件会导致无法访问文件错误。
好不容易找到了,不过解决方法很简单,Waiting“让任务完成执行”:
using (var wc = new WebClient())
{
var tskResult = wc.UploadFileTaskAsync(_address, _fileName);
tskResult.Wait();
}
【讨论】:
我遇到了这个问题,按照下面的代码解决了
var _path=MyFile.FileName;
using (var stream = new FileStream
(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
// Your Code! ;
}
【讨论】:
我下面的代码解决了这个问题,但我建议 首先您需要了解导致此问题的原因并尝试通过更改代码找到的解决方案
我可以提供另一种方法来解决此问题,但更好的解决方案是检查您的编码结构并尝试分析导致这种情况发生的原因,如果您没有找到任何解决方案,那么您可以使用下面的代码
try{
Start:
///Put your file access code here
}catch (Exception ex)
{
//by anyway you need to handle this error with below code
if (ex.Message.StartsWith("The process cannot access the file"))
{
//Wait for 5 seconds to free that file and then start execution again
Thread.Sleep(5000);
goto Start;
}
}
【讨论】:
GC.*(),那么您的代码可能还有其他问题。 2) 消息已本地化且脆弱,请改用 HRESULT。 3) 你可能想和Task.Delay() 一起睡觉(在许多情况下,十秒有点过分)。 4)您没有退出条件:此代码可能会永远挂起。 5) 这里绝对不需要goto。 6) 捕获Exception 通常是个坏主意,在这种情况下也是因为... 6) 如果发生其他任何事情,那么您就是在接受错误。
GC.Collect() 的唯一(我强调唯一)情况是在处理一些 COM 对象时。
StackOverflowException或OutOfMemoryException(吞并重复)?在这些错误之后,您确定您的程序数据仍然正确吗?不,编写可以处理和恢复内存不足情况的代码非常困难。非常难,绝对不会吞下并忽略错误。
goto 执行while (true) 循环,捕获所有异常,然后使用错误消息进行区分。没有什么好理解的了,即使是一次性脚本也只是次优(恕我直言)。