有没有办法知道有多少缓冲区留给 FileSystemwatcher?

Is there a way to know how much buffer is left for FileSystemwatcher?

当我查询

fileSystemWatcher.InternalBufferSize

它将给出分配给观察者的总内部缓冲区大小。但是我想知道(在调试期间)观察者的缓冲区大小还有多少可以使用,当我在事件处理程序方法中使用上面的语句时(比如写操作)它总是给我分配的总缓冲区大小给守望者。有什么方法可以获取缓冲区的剩余大小?

其他问题:

this 的回答可以看出,事件是在与接收事件的线程不同的线程上处理的。假设我们有许多并发事件来自正在监视文件的单个 Watcher。我的想法(如果我错了请纠正我)接收事件信息的主线程将为每个事件生成一个新线程,并且事件的处理将在不同的线程上进行。所以我想问一下:

  1. Will the main thread wait to finish the processing of all the events?
  2. Which thread will clear the internal buffer associated with the Watcher and when?
  3. I have read at lots of places that the handler method should take as minimum time as possible or we can get InternalBufferOverflow Exception. So, is it safe to assume that the Internal Buffer for the Watcher is only cleaned up when the thread(s) (I can't say one or all, but want to ask from you) which are processing the handler method has processed the method?

不,您不知道还剩多少缓冲区。

它是隐藏在名为 FSWAsyncResult 的内部 class 中的实现细节。如果您掌握了 class 的实例及其包含的缓冲区字节数组,您仍然无法可靠地回答 还剩下多少 space 作为该字节 array 仅作为调用 ReadDirectoryChangesW

的结果的保留内存

在这个答案的底部找到一个精简的逆向工程版本,用于监视文件夹中的文件更改。它的逻辑和代码与您在真正的 FileSystemWatcher 中找到的相匹配。我没有费心用它们的正确含义替换魔法常量。 它很管用™。不要忘记更改构建设置 unsafe,因为代码会大量使用指针和本机结构。我去掉了所有错误处理...

如果你遵循下面的代码,你会注意到只有一个地方创建了 byte[] 缓冲区,而且只发生一次。重复使用相同的缓冲区。阅读文档、博客和 worker and I/O threads 我了解到 ReadDirectoryChangesW 用于以 I/O 完成方式发出回调。对于托管世界来说这并不重要,这只是另一个线程。

回调安排在托管线程池线程上。有时您会得到与以前相同的托管 ID,当它很忙时您会得到几个。在该线程上执行 CompletionStatusChanged。该方法负责处理当前字节缓冲区中存在的所有事件。请注意,我包含了一个 sizeused 变量,因此您可以看到缓冲区中存在的有效数据的实际大小。对于每个事件,它发现它 raises/calls 同步事件的订阅者(所以在同一个线程上)。一旦完成,它会再次调用 Monitor 并使用 相同的 byte[] buffer 它刚刚处理过。 CompletionStatusChanged 执行期间的任何文件更改都由 OS 保存并在下次调用 CompletionStatusChanged 时发送。

tl;博士; 以下是对您的问题的回答摘要:

... I want to know (during debugging) how much buffer size for the Watcher is left and can be used

只使用了一个缓冲区,知道使用了多少或还剩多少是没有意义的。一旦你的事件处理程序被调用,缓冲区就会被重置并再次从 0 开始。当事件多于字节缓冲区可以处理时,它会引发异常。

  1. Will the main thread wait to finish the processing of all the events?

OS 将通过 IOCompletionPort 发出异步回调,但它将自身显示为普通托管线程池线程。该线程将处理当前缓冲区中的所有事件并调用事件处理程序。

  1. Which thread will clear the internal buffer associated with the Watcher and when?

执行CompletionStatusChanged方法的线程。请注意,在我的测试中,缓冲区从未被清除(如填充为零)。数据刚刚被覆盖。

  1. I have read at lots of places that the handler method should take as minimum time as possible or we can get InternalBufferOverflow Exception. So, is it safe to assume that the Internal Buffer for the Watcher is only cleaned up when the thread(s) (I can't say one or all, but want to ask from you) which are processing the handler method has processed the method?

您应该使处理过程尽可能短,因为只有一个线程会调用所有事件处理程序,最后它必须再次调用 ReadDirectoryChangesW。在此期间,它将跟踪文件更改。当这些文件更改事件不适合缓冲区时,它将在下次调用完成方法时引发 InternalBufferOverflow。

设置

一个简单的控制台应用程序,在等待事件时使用 ReadLine 来保持它运行。

static object instance = new object(); // HACK
static SafeFileHandle hndl; // holds our filehandle (directory in this case)

static void Main(string[] args)
{

    // the folder to watch
    hndl = NativeMethods.CreateFile(@"c:\temp\delete", 1, 7, IntPtr.Zero, 3, 1107296256, new SafeFileHandle(IntPtr.Zero, false));
    // this selects IO completion threads in the ThreadPool
    ThreadPool.BindHandle(hndl);

    // this starts the actual listening
    Monitor(new byte[4096]);

    Console.ReadLine();

}

监控

此方法负责创建 Native 结构和助手实例 class 以充当 IAsyncResult 实现。
此方法还调用 ReadDirectoryChangesW 并选择将其设置为异步完成的参数组合,使用 IOCompletinPorts。有关这些选项的更多背景信息,请参阅 Understanding ReadDirectoryChangesW - Part 1

static unsafe void Monitor(byte[] buffer)
{

    Overlapped overlapped = new Overlapped();

    // notice how the buffer goes here as instance member on AsyncResult.
    // Arrays are still Reference types.      
    overlapped.AsyncResult = new AsyncResult { buffer = buffer };
    // CompletionStatusChanged is the method that will be called
    // when filechanges are detected
    NativeOverlapped* statusChanged = overlapped.Pack(new IOCompletionCallback(CompletionStatusChanged), buffer);

    fixed (byte* ptr2 = buffer)
    {
        int num;
        // this where the magic starts
        NativeMethods.ReadDirectoryChangesW(hndl, 
          new HandleRef(instance, (IntPtr)((void*)ptr2)), 
          buffer.Length, 
          1, 
          (int)(NotifyFilters.FileName | NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Attributes), 
          out num, 
          statusChanged, 
          new HandleRef(null, IntPtr.Zero));
    }

}

完成状态已更改

一旦检测到文件更改,OS 就会调用 CompletionStatusChanged 方法。在 Overlapped 结构中,我们将在解包后发现我们之前的 ResultAsync 实例,其中包含一个已填充的缓冲区。该方法的其余部分然后通过读取数组中任何后续事件的偏移量以及标志和文件名来解码字节数组。

// this gets called by a ThreadPool IO Completion thread
static unsafe void CompletionStatusChanged(uint errorCode, uint numBytes, NativeOverlapped* overlappedPointer)
    {
        var sb = new StringBuilder();

        Overlapped overlapped = Overlapped.Unpack(overlappedPointer);
        var result = (AsyncResult) overlapped.AsyncResult;

        var position = 0;
        int offset;
        int flags;
        int sizeused = 0;
        string file;
        // read the buffer,
        // that can contain multiple events
        do
        {
            fixed (byte* ptr = result.buffer)
            {
                // process FILE_NOTIFY_INFORMATION
                // see https://msdn.microsoft.com/en-us/library/windows/desktop/aa364391(v=vs.85).aspx
                offset = ((int*)ptr)[position / 4];
                flags = ((int*)ptr + position / 4)[1];
                int len = ((int*)ptr + position / 4)[2];
                file = new string((char*)ptr + position / 2 + 6, 0, len / 2);
                sizeused = position + len + 14; 
            }
            sb.AppendFormat("#thread {0},  event: {1}, {2}, {3}, {4}\r\n", Thread.CurrentThread.ManagedThreadId, position, offset, flags, file);
            // in the real FileSystemWatcher here the several events are raised
            // so that uses the same thread this code is on.
            position += offset;
        } while (offset != 0);

        // my own logging
        sb.AppendFormat(" === buffer used: {0} ==== ", sizeused);

        Console.WriteLine(sb);

        // start again, reusing the same buffer:
        Monitor(result.buffer);
    }
}

辅助方法

AsyncResult 实现 IAsyncResult(全部为空)并将成员保存到字节数组缓冲区。
NativeMethods 正是它们的名称:Native 调用 WinAPI 的入口点。

class AsyncResult : IAsyncResult
{
    internal byte[] buffer;
    // default implementation of the interface left out

    // removed default implementation for brevity
}

static class NativeMethods
{
    [DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Auto)]
    public static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, SafeFileHandle hTemplateFile);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public unsafe static extern bool ReadDirectoryChangesW(SafeFileHandle hDirectory, HandleRef lpBuffer, int nBufferLength, int bWatchSubtree, int dwNotifyFilter, out int lpBytesReturned, NativeOverlapped* overlappedPointer, HandleRef lpCompletionRoutine);
}