在C#中使用LockFileEX

背景

我正在尝试在我的C#应用​​程序中实现块文件锁定。 如果内置的FileStream.Lock方法无法获取锁定,则会引发exception。

底层的LockFile方法返回状态代码但是我不想使用自旋锁来等待文件被解锁。

有没有人在C#中有任何代码片段,显示如何使用wait句柄正确构造OVERLAPPED结构并将其传递给LockFileEx并等待操作完成? 我试图避免使用Overlapped.Pack方法,因为它们不安全,但主要是因为它们需要IOCompletionCallback ,这不是我想要实现的。

我有声明,但OverLapped结构的构造和使用似乎有点复杂。

注意 :我知道我需要手动固定重叠结构,直到等待完成。 我目前的代码如下:

 ManualResetEvent evt = new ManualResetEvent(false); OVERLAPPED overlapped = new OVERLAPPED(); overlapped.OffsetLow = offsetLow; overlapped.OffsetHigh = offsetHigh; overlapped.hEvent = evt.SafeHandle; GCHandle h = GCHandle.Alloc(overlapped, GCHandleType.Pinned); int hr = Win32.LockFileEX(_handle, LockFlags.Exclusive, 0, offsetLow, offsetHigh, GCHandle.ToIntPtr(h)); if(hr == 0) { int error = Marshal.GetLastWin32Error(); if(error = Win32.ERROR_IO_PENDING) { evt.WaitOne(); } else { //ohpoo } } 

解析度

最终按我的意愿工作的代码是:

 [StructLayout(LayoutKind.Sequential)] public struct OVERLAPPED { public uint internalLow; public uint internalHigh; public uint offsetLow; public uint offsetHigh; public IntPtr hEvent; } [DllImport("Kernel32.dll", SetLastError = true)] private static extern bool LockFileEx(SafeFileHandle handle, uint flags, uint reserved, uint countLow, uint countHigh, ref OVERLAPPED overlapped); private const uint LOCKFILE_EXCLUSIVE_LOCK = 0x00000002; public static void Lock(FileStream stream, ulong offset, ulong count) { uint countLow = (uint)count; uint countHigh = (uint)(count >> 32); OVERLAPPED overlapped = new OVERLAPPED() { internalLow = 0, internalHigh = 0, offsetLow = (uint)offset, offsetHigh = (uint)(offset >> 32), hEvent = IntPtr.Zero, }; if (!LockFileEx(stream.SafeFileHandle, LOCKFILE_EXCLUSIVE_LOCK, 0, countLow, countHigh, ref overlapped)) { //TODO: throw an exception } } 

此代码将阻止,直到可以获取该区域的独占锁定。

以下内容应与“良好解决方案”“非常接近”。 我真正不喜欢的唯一部分是使用reflection来访问mscorlib内部方法,但该方法在将Win32错误代码转换为IOException方面做得非常出色 。 由于您已经拥有NativeOverlapped*不安全代码, NativeOverlapped*权限不是问题。

可以通过创建SafeFileLockHandle或类似方法来进一步改进,以提供轻量级IDisposable对象来解锁从CriticalFinalizerObject派生的文件。

  private const uint LOCKFILE_EXCLUSIVE_LOCK = 0x00000002; private static readonly Action WinIOError; static Win32Native() { BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; var winIOErrorMethod = typeof(string).Assembly.GetType("System.IO.__Error").GetMethod("WinIOError", bindingAttr, null, Type.EmptyTypes, null); WinIOError = (Action)Delegate.CreateDelegate(typeof(Action), winIOErrorMethod); } public static void LockFile(SafeFileHandle handle, bool exclusive, long offset, long length, Action action) { if (handle == null) throw new ArgumentNullException("handle"); if (handle.IsInvalid) throw new ArgumentException("An invalid file handle was specified.", "handle"); if (offset < 0) throw new ArgumentOutOfRangeException("The offset cannot be negative.", "offset"); if (length < 0) throw new ArgumentOutOfRangeException("The length cannot be negative.", "length"); if (action == null) throw new ArgumentNullException("action"); LockFileUnsafe(handle, exclusive, offset, length, action); } private static unsafe void LockFileUnsafe(SafeFileHandle handle, bool exclusive, long offset, long length, Action action) { Overlapped overlapped = new Overlapped(); overlapped.OffsetHigh = (int)(offset >> 32); overlapped.OffsetLow = (int)offset; IOCompletionCallback callback = (errorCode, numBytes, nativeOverlapped) => { try { action(); } finally { Overlapped.Free(nativeOverlapped); } }; NativeOverlapped* native = overlapped.Pack(callback, null); uint flags = exclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0; if (!LockFileEx(handle, flags, 0, (int)length, (int)(length >> 32), native)) { Overlapped.Free(native); WinIOError(); } } [DllImport("kernel32.dll", SetLastError = true)] private static unsafe extern bool LockFileEx(SafeFileHandle handle, uint flags, uint mustBeZero, int countLow, int countHigh, NativeOverlapped* overlapped); 

Reflector有答案(和pinvoke.net和http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/7217a8d3-d36d-43c9-ad4f-ad638a9ac1de有声明)