如何检查文件locking?

有没有办法检查一个文件是否被locking,而不使用try / catch块?

现在,我知道的唯一方法就是打开文件并捕获任何System.IO.IOException

不,不幸的是,如果你仔细想一想,那么这个信息将毫无价值,因为这个文件可能会在下一秒被locking(阅读时间很短)。

为什么你特别需要知道文件是否被locking? 知道这可能会给我们一些其他的方式给你很好的build议。

如果你的代码看起来像这样:

 if not locked then open and update file 

然后在两行之间,另一个进程可以很容易地locking文件,给你同样的问题,你试图避免开始:例外。

当我遇到类似的问题时,我完成了以下代码:

 public bool IsFileLocked(string filePath) { try { using (File.Open(filePath, FileMode.Open)){} } catch (IOException e) { var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1); return errorCode == 32 || errorCode == 33; } return false; } 

其他答案依靠旧信息。 这提供了一个更好的解决scheme。

很久以前,因为Windows根本没有跟踪这些信息,所以无法可靠地获取进程locking文件的列表。 为了支持重新启动pipe理器API ,现在跟踪这些信息。 重新启动pipe理器API可从Windows Vista和Windows Server 2008( 重新启动pipe理器:运行时要求 )开始。

我将放在一个文件path的代码放在一起,并返回所有正在locking该文件的进程的List<Process>

 static public class FileUtil { [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; } } 

UPDATE

以下是有关如何使用Restart Manager API的示例代码的另一个讨论 。

您还可以检查是否有任何进程正在使用此文件,并显示您必须closures才能继续执行的程序列表。

 public static string GetFileProcessName(string filePath) { Process[] procs = Process.GetProcesses(); string fileName = Path.GetFileName(filePath); foreach (Process proc in procs) { if (proc.MainWindowHandle != new IntPtr(0) && !proc.HasExited) { ProcessModule[] arr = new ProcessModule[proc.Modules.Count]; foreach (ProcessModule pm in proc.Modules) { if (pm.ModuleName == fileName) return proc.ProcessName; } } } return null; } 

而不是使用interop你可以使用.NET FileStream类的方法locking和解锁:

FileStream.Lock http://msdn.microsoft.com/en-us/library/system.io.filestream.lock.aspx

FileStream.Unlock http://msdn.microsoft.com/en-us/library/system.io.filestream.unlock.aspx

你可以通过在你感兴趣的文件的区域上调用LockFile 。这不会抛出一个exception,如果成功,你将locking文件的那一部分(由你的进程持有),那个锁将会是直到你调用UnlockFile或者你的进程结束。

DixonD的优秀答案的变体(上图)。

 public static bool TryOpen(string path, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, TimeSpan timeout, out Stream stream) { var endTime = DateTime.Now + timeout; while (DateTime.Now < endTime) { if (TryOpen(path, fileMode, fileAccess, fileShare, out stream)) return true; } stream = null; return false; } public static bool TryOpen(string path, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, out Stream stream) { try { stream = File.Open(path, fileMode, fileAccess, fileShare); return true; } catch (IOException e) { if (!FileIsLocked(e)) throw; stream = null; return false; } } private const uint HRFileLocked = 0x80070020; private const uint HRPortionOfFileLocked = 0x80070021; private static bool FileIsLocked(IOException ioException) { var errorCode = (uint)Marshal.GetHRForException(ioException); return errorCode == HRFileLocked || errorCode == HRPortionOfFileLocked; } 

用法:

 private void Sample(string filePath) { Stream stream = null; try { var timeOut = TimeSpan.FromSeconds(1); if (!TryOpen(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, timeOut, out stream)) return; // Use stream... } finally { if (stream != null) stream.Close(); } } 

然后在两行之间,另一个进程可以很容易地locking文件,给你同样的问题,你试图避免开始:例外。

但是,这样,您就会知道这个问题是暂时的,并且会在稍后重试。 (例如,你可以编写一个线程,如果在尝试写入时遇到locking,则会一直重试,直到locking消失。

另一方面,IOException本身不够具体,locking是导致IO失败的原因。 有可能是不是暂时的原因。

下面是DixonD代码的一个变体,它增加了等待文件解锁的秒数,然后重试:

 public bool IsFileLocked(string filePath, int secondsToWait) { bool isLocked = true; int i = 0; while (isLocked && ((i < secondsToWait) || (secondsToWait == 0))) { try { using (File.Open(filePath, FileMode.Open)) { } return false; } catch (IOException e) { var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1); isLocked = errorCode == 32 || errorCode == 33; i++; if (secondsToWait !=0) new System.Threading.ManualResetEvent(false).WaitOne(1000); } } return isLocked; } if (!IsFileLocked(file, 10)) { ... } else { throw new Exception(...); } 

您可以通过首先尝试读取或自行locking来查看文件是否被locking。

请参阅我的答案在这里获取更多信息 。

我最终做的是:

 internal void LoadExternalData() { FileStream file; if (TryOpenRead("filepath/filename", 5, out file)) { using (file) using (StreamReader reader = new StreamReader(file)) { // do something } } } internal bool TryOpenRead(string path, int timeout, out FileStream file) { bool isLocked = true; bool condition = true; do { try { file = File.OpenRead(path); return true; } catch (IOException e) { var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1); isLocked = errorCode == 32 || errorCode == 33; condition = (isLocked && timeout > 0); if (condition) { // we only wait if the file is locked. If the exception is of any other type, there's no point on keep trying. just return false and null; timeout--; new System.Threading.ManualResetEvent(false).WaitOne(1000); } } } while (condition); file = null; return false; } 

同样的事情,但在Powershell

 function Test-FileOpen { Param ([string]$FileToOpen) try { $openFile =([system.io.file]::Open($FileToOpen,[system.io.filemode]::Open)) $open =$true $openFile.close() } catch { $open = $false } $open }