如何检查文件锁?

有没有什么方法可以不使用try/catch块来检查文件是否被锁定?

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

227040 次浏览

不,不幸的是,如果你仔细想想,这些信息无论如何都是毫无价值的,因为文件可能在下一秒就被锁定(阅读:短时间跨度)。

为什么您特别需要知道文件是否被锁定?知道了这一点,我们可能会以其他方式给你一些好的建议。

如果你的代码是这样的:

if not locked then
open and update file

然后,在这两行之间,另一个进程可以很容易地锁定文件,给您带来您一开始试图避免的相同问题:异常。

然后,在这两行之间,另一个进程可以很容易地锁定文件,给您带来您一开始试图避免的相同问题:异常。

但是,通过这种方式,您将知道问题是暂时的,并稍后重试。(例如,您可以编写一个线程,如果在尝试写入时遇到锁,则会不断重试,直到锁消失。)

另一方面,IOException本身还不够特定,以至于锁定是导致IO失败的原因。可能有一些不是暂时的原因。

您可以通过尝试自己先读取或锁定文件来查看文件是否被锁定。

请在这里查看我的回答以了解更多信息

你可以在你感兴趣的文件区域上通过互操作调用LockFile。这将不会抛出异常,如果它成功,你将对文件的那部分有一个锁(由你的进程持有),这个锁将一直持有,直到你调用UnlockFile或你的进程死亡。

不使用互操作,你可以使用。net FileStream类方法Lock和Unlock:

< p >文件流。锁 http://msdn.microsoft.com/en-us/library/system.io.filestream.lock.aspx < / p > < p >文件流。解锁 http://msdn.microsoft.com/en-us/library/system.io.filestream.unlock.aspx < / p >

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

public class FileManager
{
private string _fileName;


private int _numberOfTries;


private int _timeIntervalBetweenTries;


private FileStream GetStream(FileAccess fileAccess)
{
var tries = 0;
while (true)
{
try
{
return File.Open(_fileName, FileMode.Open, fileAccess, Fileshare.None);
}
catch (IOException e)
{
if (!IsFileLocked(e))
throw;
if (++tries > _numberOfTries)
throw new MyCustomException("The file is locked too long: " + e.Message, e);
Thread.Sleep(_timeIntervalBetweenTries);
}
}
}


private static bool IsFileLocked(IOException exception)
{
int errorCode = Marshal.GetHRForException(exception) & ((1 << 16) - 1);
return errorCode == 32 || errorCode == 33;
}


// other code


}

您还可以检查是否有任何进程正在使用此文件,并像安装程序一样显示必须关闭才能继续的程序列表。

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

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

下面是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(...);
}

我最后做的是:

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

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

很久以前,可靠地获取锁定文件的进程列表是不可能的,因为Windows根本没有跟踪这些信息。为了支持重启管理器API,该信息现在被跟踪。重启管理器API可以从Windows Vista和Windows Server 2008 (重新启动管理器:运行时需求)开始使用。

我将获取文件路径的代码放在一起,并返回锁定该文件的所有进程的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;
}
}

更新

这里是关于如何使用重启管理器API的另一个使用示例代码进行讨论

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