我有一些代码,当它执行时,它抛出一个IOException,说
进程无法访问文件'filename',因为它正在被 另一个进程
这意味着什么?我能做些什么?
我有一些代码,当它执行时,它抛出一个IOException,说
进程无法访问文件'filename',因为它正在被 另一个进程
这意味着什么?我能做些什么?
当前回答
该错误表示另一个进程正在试图访问该文件。可能您或其他人在尝试向其写入时打开了它。“Read”或“Copy”通常不会导致这种情况,但对其写入或调用delete则会。
正如其他回答所提到的,有一些基本的事情可以避免这种情况:
In FileStream operations, place it in a using block with a FileShare.ReadWrite mode of access. For example: using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) { } Note that FileAccess.ReadWrite is not possible if you use FileMode.Append. I ran across this issue when I was using an input stream to do a File.SaveAs when the file was in use. In my case I found, I didn't actually need to save it back to the file system at all, so I ended up just removing that, but I probably could've tried creating a FileStream in a using statement with FileAccess.ReadWrite, much like the code above. Saving your data as a different file and going back to delete the old one when it is found to be no longer in use, then renaming the one that saved successfully to the name of the original one is an option. How you test for the file being in use is accomplished through the List<Process> lstProcs = ProcessHandler.WhoIsLocking(file); line in my code below, and could be done in a Windows service, on a loop, if you have a particular file you want to watch and delete regularly when you want to replace it. If you don't always have the same file, a text file or database table could be updated that the service always checks for file names, and then performs that check for processes & subsequently performs the process kills and deletion on it, as I describe in the next option. Note that you'll need an account user name and password that has Admin privileges on the given computer, of course, to perform the deletion and ending of processes. When you don't know if a file will be in use when you are trying to save it, you can close all processes that could be using it, like Word, if it's a Word document, ahead of the save. If it is local, you can do this: ProcessHandler.localProcessKill("winword.exe"); If it is remote, you can do this: ProcessHandler.remoteProcessKill(computerName, txtUserName, txtPassword, "winword.exe"); where txtUserName is in the form of DOMAIN\user. Let's say you don't know the process name that is locking the file. Then, you can do this: 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); } Note that file must be the UNC path: \\computer\share\yourdoc.docx in order for the Process to figure out what computer it's on and p.MachineName to be valid. Below is the class these functions use, which requires adding a reference to System.Management. The code was 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; } }
其他回答
原因是什么?
错误消息非常清楚:您试图访问一个文件,但它无法访问,因为另一个进程(甚至是相同的进程)正在对它做一些事情(并且它不允许任何共享)。
调试
它可能很容易解决(也可能很难理解),这取决于您的具体场景。让我们看看。
您的进程是唯一访问该文件的进程 你确定另一个过程是你自己的过程。如果您知道您在程序的另一部分打开了该文件,那么首先您必须检查在每次使用后是否正确地关闭了文件句柄。下面是这个错误的代码示例:
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
此模式还将确保在异常情况下文件不会保持打开状态(这可能是文件正在使用的原因:出错了,没有人关闭它;请看这篇文章的例子)。
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);
}
}
请注意我们在StackOverflow上经常看到的一个常见错误:
var stream = File.Open(path, FileOpen.Read);
var content = File.ReadAllText(path);
在这种情况下,ReadAllText()将失败,因为文件正在使用中(前一行为file . open())。事先打开文件不仅没有必要,而且是错误的。这同样适用于所有不返回您正在处理的文件句柄的文件函数:File. readalltext (), File. writealltext (), File. readalllines (), File. writealllines()和其他(如File. appendallxyz()函数)都将自行打开和关闭文件。
您的进程并不是唯一访问该文件的进程 如果您的进程不是唯一访问该文件的进程,那么交互就会更加困难。重试模式将有所帮助(如果文件不应该被其他人打开,但它却被打开了,那么您需要一个像Process Explorer这样的实用程序来检查谁正在做什么)。
避免的方法
如果适用,总是使用using语句打开文件。如前一段所述,它将积极地帮助您避免许多常见错误(关于如何不使用它的示例,请参阅这篇文章)。
如果可能的话,尝试决定谁拥有对特定文件的访问权,并通过一些众所周知的方法集中访问权。例如,如果您有一个用于程序读取和写入的数据文件,那么您应该将所有I/O代码装入单个类中。这将使调试更容易(因为您总是可以在那里放置一个断点,并查看谁在做什么),而且它将成为多个访问的同步点(如果需要)。
不要忘记I/O操作总是会失败,一个常见的例子是:
if (File.Exists(path))
File.Delete(path);
如果有人在file . exists()之后但在file . delete()之前删除文件,那么它将在一个你可能错误地认为安全的地方抛出IOException。
只要可能,就应用重试模式,如果您正在使用FileSystemWatcher,则考虑延迟操作(因为您会收到通知,但应用程序可能仍在专门处理该文件)。
先进的场景 这并不总是那么容易,所以您可能需要与其他人共享访问权限。例如,如果你要从头读到尾写,你至少有两种选择。
1)使用适当的同步函数共享相同的FileStream(因为它不是线程安全的)。请看这个和这个帖子的例子。
2)使用FileShare枚举命令操作系统允许其他进程(或你自己进程的其他部分)并发访问同一个文件。
using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.Read))
{
}
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个进程可以一起从同一个文件中读取,但只有一个进程应该写入,在受控的情况下,你甚至可以启用并发写入,但这不能在这个答案中的几个文本段落中普遍化。
是否可以解锁另一个进程使用的文件?这并不总是安全的,也不那么容易,但是的,这是可能的。
我有下面的场景会导致同样的错误:
上传文件到服务器 然后在旧文件上传后删除它们
大多数文件都很小,但也有少数文件很大,因此试图删除这些文件会导致无法访问文件的错误。
然而,要找到这个问题并不容易,解决方法很简单,就是“等待任务完成执行”:
using (var wc = new WebClient())
{
var tskResult = wc.UploadFileTaskAsync(_address, _fileName);
tskResult.Wait();
}
我有一个非常具体的情况,我得到了一个“IOException:进程不能访问文件'文件路径'”的行
File.Delete(fileName);
在一个NUnit测试中,看起来像这样:
Assert.Throws<IOException>(() =>
{
using (var sr = File.OpenText(fileName) {
var line = sr.ReadLine();
}
});
File.Delete(fileName);
NUnit 3使用了所谓的“隔离上下文”来进行异常断言。这可能运行在一个单独的线程上。
我的解决办法是把文件。在同一上下文中删除。
Assert.Throws<IOException>(() =>
{
try
{
using (var sr = File.OpenText(fileName) {
var line = sr.ReadLine();
}
}
catch
{
File.Delete(fileName);
throw;
}
});
正如本文中的其他回答所指出的,要解决这个错误,您需要仔细检查代码,以了解文件被锁定的位置。
在我的例子中,我在执行移动操作之前将文件作为电子邮件附件发送出去。
所以文件被锁定了几秒钟,直到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
}
}
我下面的代码解决这个问题,但我建议 首先,你需要了解是什么导致了这个问题,并尝试通过修改代码来找到解决方案
我可以给出另一种方法来解决这个问题,但更好的解决方案是检查你的编码结构,并尝试分析是什么导致这种情况发生,如果你没有找到任何解决方案,那么你可以使用下面的代码
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;
}
}