异步 ReadDirectoryChangesW - GetQueuedCompletionStatus 总是超时

发布于 2024-11-09 22:11:44 字数 3624 浏览 0 评论 0原文

正如听起来的那样,我正在尝试使用 IO Completion 进行异步 ReadDirectoryChangesW ,但它不起作用,具体来说,GetLastError 重复返回 258 (GetQueuedCompletionStatus暂停)。

我有结构:

typedef struct dirinfo_struct
{
    HANDLE hDirFH;           // directory handle
    OVERLAPPED Overlapped;   // overlapped storage
    int len_buffer;          // buffer length
    wchar_t* buffer;         // buffer itself
    wchar_t* directory_name; // target name
} dirinfo_t;

typedef struct dirmon_struct
{
    HANDLE hDirOPPort;       // handle to the IO port.
    dirinfo_t* dirinfo;      // pointer to the struct above.
} dirmon_t;

用于存储相关信息。这是初始化的:

dirinfo_t* t = malloc(1*sizeof(dirinfo_t));
dirmon_t* d = malloc(1*sizeof(dirmon_t));
dirinfo_init(t); // does t->buffer = malloc(8192*sizeof(wchar_t));

然后我创建我的目录句柄和 com 端口:

t->hDirFH = CreateFile(L"C:\\test",
                        FILE_LIST_DIRECTORY,
                        FILE_SHARE_READ|FILE_SHARE_WRITE,
                        NULL,
                        OPEN_EXISTING,
                        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                        NULL); 
d->dirinfo = t;
d->hDirOPPort = CreateIoCompletionPort(d->dirinfo->hDirFH, 
                                       NULL,       
                                       (ULONG_PTR)(d->dirinfo), 
                                       1); 

然后我通过 d 将此信息传递到新线程。现在,在所说的新线程上,我有:

bResultQ = GetQueuedCompletionStatus(d->hDirOPPort, lpBytes, 
                                     (ULONG_PTR*)d->dirinfo,    
                                     lpOverlapped, 1000);

if ( bResultQ )
{
    bResultR = ReadDirectoryChangesW(d->dirinfo->hDirFH, 
                                     (void*)d->dirinfo->buffer, 
                                     8192, TRUE,
                                     FILE_NOTIFY_CHANGE_FILE_NAME | 
                                     FILE_NOTIFY_CHANGE_DIR_NAME |
                                     FILE_NOTIFY_CHANGE_ATTRIBUTES | 
                                     FILE_NOTIFY_CHANGE_SIZE |
                                     FILE_NOTIFY_CHANGE_LAST_WRITE | 
                                     FILE_NOTIFY_CHANGE_LAST_ACCESS | 
                                     FILE_NOTIFY_CHANGE_CREATION | 
                                     FILE_NOTIFY_CHANGE_SECURITY,
                                     lpReadDirBytes,
                                     &d->dirinfo->Overlapped,
                                     NULL );
} 
else
{
    printf("GetQueuedCompletionStatus(): Failed, ");
    errorcode = GetLastError();
    printf("Error Code %d\n", errorcode);
    Sleep(500);
}

所以我将其设置为运行,并且我愉快地得到了超时(258 个错误),因为目录没有更改,所以我应该这样做。但是,即使我更改目录,我仍然收到错误消息;换句话说,这些更改没有被采纳。这让我相信我的设置不正确。

有什么想法吗?

注意事项:

  • 具有讽刺意味的是,这最终将通过 pywin32 转换为 Python。然而,在我理解这在 C 中应该如何工作之前,我不会去那里。
  • 同步 ReadDirectoryChangesW 不是一个选项。如果没有事件被触发,我需要该线程超时,以便它可以检查它是否应该仍在运行。
  • 我专门用 C 编写,而不是 C++。
  • FindFirstChangeNotification 等也不是一个选项 - 我不想不断比较目录列表来找出发生了什么变化。

其他注意事项:

  • 目录存在,该句柄不为 NULL。对于兼容手柄也是如此。
  • 所有内容都被传递到线程

我从代码项目中查看了 CDirectoryChangeWatcher ,但是除了使用 C++ 和更多线程之外,我看不出我在做什么不同的事情。如果我遗漏了什么,请随时指出!

输出(如果有帮助的话)基本上是重复的,无论我如何更改有问题的目录。

GetQueuedCompletionStatus(): Failed, Error Code 258

Exactly as it sounds, I'm attempting asynchronous ReadDirectoryChangesW with IO Completion and it isn't working, specifically, GetLastError repeatedly returns 258 (GetQueuedCompletionStatus timeout).

I have structs:

typedef struct dirinfo_struct
{
    HANDLE hDirFH;           // directory handle
    OVERLAPPED Overlapped;   // overlapped storage
    int len_buffer;          // buffer length
    wchar_t* buffer;         // buffer itself
    wchar_t* directory_name; // target name
} dirinfo_t;

typedef struct dirmon_struct
{
    HANDLE hDirOPPort;       // handle to the IO port.
    dirinfo_t* dirinfo;      // pointer to the struct above.
} dirmon_t;

for storing the relevant information. This is initialised:

dirinfo_t* t = malloc(1*sizeof(dirinfo_t));
dirmon_t* d = malloc(1*sizeof(dirmon_t));
dirinfo_init(t); // does t->buffer = malloc(8192*sizeof(wchar_t));

Then I create my Directory Handle and com port:

t->hDirFH = CreateFile(L"C:\\test",
                        FILE_LIST_DIRECTORY,
                        FILE_SHARE_READ|FILE_SHARE_WRITE,
                        NULL,
                        OPEN_EXISTING,
                        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                        NULL); 
d->dirinfo = t;
d->hDirOPPort = CreateIoCompletionPort(d->dirinfo->hDirFH, 
                                       NULL,       
                                       (ULONG_PTR)(d->dirinfo), 
                                       1); 

Then I pass this information via d to a new thread. Now on said new thread I have:

bResultQ = GetQueuedCompletionStatus(d->hDirOPPort, lpBytes, 
                                     (ULONG_PTR*)d->dirinfo,    
                                     lpOverlapped, 1000);

if ( bResultQ )
{
    bResultR = ReadDirectoryChangesW(d->dirinfo->hDirFH, 
                                     (void*)d->dirinfo->buffer, 
                                     8192, TRUE,
                                     FILE_NOTIFY_CHANGE_FILE_NAME | 
                                     FILE_NOTIFY_CHANGE_DIR_NAME |
                                     FILE_NOTIFY_CHANGE_ATTRIBUTES | 
                                     FILE_NOTIFY_CHANGE_SIZE |
                                     FILE_NOTIFY_CHANGE_LAST_WRITE | 
                                     FILE_NOTIFY_CHANGE_LAST_ACCESS | 
                                     FILE_NOTIFY_CHANGE_CREATION | 
                                     FILE_NOTIFY_CHANGE_SECURITY,
                                     lpReadDirBytes,
                                     &d->dirinfo->Overlapped,
                                     NULL );
} 
else
{
    printf("GetQueuedCompletionStatus(): Failed, ");
    errorcode = GetLastError();
    printf("Error Code %d\n", errorcode);
    Sleep(500);
}

So I set this off running and I merrily get timeouts (258 errors) as I should since the directory hasn't changed. However, even if I alter the directory, I'm still getting error messages; in other words those alterations are not being picked up. Which leads me to believe I've got this set up incorrectly.

Any ideas?

Caveats:

  • Ironically, this will eventually be converted to Python via pywin32. However, until I understand how this is supposed to work in C, I'm not going there.
  • Synchronous ReadDirectoryChangesW is not an option. If no events are fired, I need the thread this is on to timeout so it can check if it should still be running.
  • I am writing in C specifically, not C++.
  • FindFirstChangeNotification etc not an option either - I don't want to continually be comparing directory listings to work out what has changed.

Other notes:

  • The Directory exists, that handle is not NULL. Likewise for the comport handle.
  • Everything's being passed to the thread

I've taken a look at CDirectoryChangeWatcher from code project, but use of C++ and many more threads aside, I can't see what I'm doing differently. Feel free to point it out if I'm missing something though!

Output, if it helps, is basically on repeat, no matter how much I alter the directory in question.

GetQueuedCompletionStatus(): Failed, Error Code 258

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

童话里做英雄 2024-11-16 22:11:44

我意识到发布代码墙通常被认为是可怕的,但这是我如何实现这一点的:

新结构:

BOOL runthread;

typedef struct overlapped_struct
{
    OVERLAPPED overlapped;
    wchar_t* buffer;
} overlapped_t;

typedef struct dirinfo_struct
{

    HANDLE hDirOPPort;
    HANDLE hDirFH;
    overlapped_t* o;
    int len_buffer;
    wchar_t* buffer;
    wchar_t* directory_name;
    ULONG_PTR CompletionKey;
} dirinfo_t;

int somekey = 1;

分配方法:

void dirinfo_init(dirinfo_t* t)
{
    t->buffer = malloc(16777216*sizeof(wchar_t));
    t->len_buffer = 16777216;
    t->o = calloc(1, sizeof(overlapped_t));
    t->o->buffer = calloc(16777216, sizeof(wchar_t));
    memset(t->o->buffer, 0, 16777216);
    memset(t->o, 0, sizeof(OVERLAPPED));
}

void dirinfo_free(dirinfo_t* t)
{
    free(t->buffer);
    free(t->o->buffer);
    free(t->o);
    free(t);
}

main() 中的重要内容是这样做的:

dirinfo_t* d = malloc(1*sizeof(dirinfo_t));
d->CompletionKey = (ULONG_PTR)&somekey;
dirinfo_init(d);

/* set up */
runthread = TRUE;
d->hDirFH = CreateFile(L"C:\\hydratest",
                FILE_LIST_DIRECTORY,
                FILE_SHARE_READ|FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                NULL); 

d->hDirOPPort = CreateIoCompletionPort(d->hDirFH, NULL, 
                      (ULONG_PTR)d->CompletionKey, 1);  

然后最后是我的等待线程。关键是:我没有传递重叠的结构。我传递的是一个包含 OVERLAPPED 以及相当数量的基于 wchar_t 的存储的结构。由于我不完全理解的原因,这有效。 编辑请参阅此答案< /a>.我相信这里的数据区域充当重叠缓冲区。

DWORD WINAPI WaitingThread(void* args)
{
    DWORD errorcode = 0;    // an error code
    BOOL bResultQ = FALSE;  // obvios=us
    BOOL bResultR = FALSE;
    DWORD NumBytes = 0; 
    FILE_NOTIFY_INFORMATION* pInfo = NULL; // the data incoming is a pointer
                                           // to this struct.
    int i = 0;
    dirinfo_t* d = (dirinfo_t*) args;      // rescue struct from thread arg.

然后我们进入主线程本身。反复试验表明您应该同时调用 ReadDirectoryW 和 GetQueueCompletionStatus。 我认为这意味着我们不应该接触来自ReadDirectoryChangeW的缓冲区**除非*我们被告知我们可以通过GetQueue >。不过,欢迎对该假设进行更正。

    while ( runthread )
    {
        bResultR = ReadDirectoryChangesW(d->hDirFH, (void*)d->buffer, 
                                          16777216, TRUE,
               FILE_NOTIFY_CHANGE_FILE_NAME  | FILE_NOTIFY_CHANGE_DIR_NAME |
               FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
               FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS | 
               FILE_NOTIFY_CHANGE_CREATION   | FILE_NOTIFY_CHANGE_SECURITY,
                                          NULL,
                                          &d->o->overlapped,
                                          NULL );
        bResultQ = GetQueuedCompletionStatus(d->hDirOPPort, 
                                             &NumBytes, &(d->CompletionKey), 
                                             (LPOVERLAPPED*)(d->o), 1000);

所以,现在我们已经调用了这些函数,然后我们测试它们是否都返回 true。 丑陋的大警告如果你的参数设置正确bResultR总是返回true,或者在我看来是这样。然而,bResultQ 根据端口上是否有新数据而变化。

        if ( bResultQ && bResultR )
        {

因此,在这里我们从 ReadDirectoryChangesW 转换该缓冲区并从结构中访问信息。

            wprintf(L"\n");
            pInfo = (FILE_NOTIFY_INFORMATION*) d->buffer;
            wprintf(L"File %s", pInfo->FileName);
            wprintf(L" changes %d\n", pInfo->Action);
            memset(d->buffer, 0, 16777216);
        }

否则,感谢托尼,您可以放心忽略 WAIT_TIMEOUT 错误,但其他任何情况都可能意味着您遇到了麻烦。

        else
        {
            errorcode = GetLastError();

            if ( errorcode == WAIT_TIMEOUT )
            {
                printf("GetQueuedCompletionStatus(): Timeout\n");
            }
            else
            {
                printf("GetQueuedCompletionStatus(): Failed\n");
                printf("Error Code %d\n", errorcode);
            }
            Sleep(500);
        }
    }   

    return 0;
}

这完成了我认为是一个有效的示例。

一些注意事项:

  • 我已将缓冲区大小设置得很大。我注意到复制了 100 个左右的文件,导致缓冲区耗尽了设置为 8192 的空间,并且到处漏掉了一两个项目。所以我不指望这总是能解决所有问题。我的解决方案是每 100 个事件,验证文件树是否是您认为使用此方法时的情况。然而,一个无限更好的解决方案是不断枚举潜在的大树。

I realise posting walls of code is generally considered horrendous, but here's how I got this working:

New structs:

BOOL runthread;

typedef struct overlapped_struct
{
    OVERLAPPED overlapped;
    wchar_t* buffer;
} overlapped_t;

typedef struct dirinfo_struct
{

    HANDLE hDirOPPort;
    HANDLE hDirFH;
    overlapped_t* o;
    int len_buffer;
    wchar_t* buffer;
    wchar_t* directory_name;
    ULONG_PTR CompletionKey;
} dirinfo_t;

int somekey = 1;

Allocation methods:

void dirinfo_init(dirinfo_t* t)
{
    t->buffer = malloc(16777216*sizeof(wchar_t));
    t->len_buffer = 16777216;
    t->o = calloc(1, sizeof(overlapped_t));
    t->o->buffer = calloc(16777216, sizeof(wchar_t));
    memset(t->o->buffer, 0, 16777216);
    memset(t->o, 0, sizeof(OVERLAPPED));
}

void dirinfo_free(dirinfo_t* t)
{
    free(t->buffer);
    free(t->o->buffer);
    free(t->o);
    free(t);
}

The important stuff from main() does this:

dirinfo_t* d = malloc(1*sizeof(dirinfo_t));
d->CompletionKey = (ULONG_PTR)&somekey;
dirinfo_init(d);

/* set up */
runthread = TRUE;
d->hDirFH = CreateFile(L"C:\\hydratest",
                FILE_LIST_DIRECTORY,
                FILE_SHARE_READ|FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                NULL); 

d->hDirOPPort = CreateIoCompletionPort(d->hDirFH, NULL, 
                      (ULONG_PTR)d->CompletionKey, 1);  

Then finally my waiting thread. Here's the key: I'm not passing an overlapped structure in. I'm passing in a structure containing an OVERLAPPED plus a fair amount of wchar_t based storage. For reasons I don't fully understand, this works. Edit see this answer. I believe the data region here acts as the overlapped buffer.

DWORD WINAPI WaitingThread(void* args)
{
    DWORD errorcode = 0;    // an error code
    BOOL bResultQ = FALSE;  // obvios=us
    BOOL bResultR = FALSE;
    DWORD NumBytes = 0; 
    FILE_NOTIFY_INFORMATION* pInfo = NULL; // the data incoming is a pointer
                                           // to this struct.
    int i = 0;
    dirinfo_t* d = (dirinfo_t*) args;      // rescue struct from thread arg.

Then we get onto the main thread itself. Trial and error suggests you're supposed to call both ReadDirectoryW AND GetQueueCompletionStatus. I think what this means is that we're supposed to not touch the buffer from ReadDirectoryChangeW **unless* we're told we can by GetQueue. Corrections on that hypothesis welcome however.

    while ( runthread )
    {
        bResultR = ReadDirectoryChangesW(d->hDirFH, (void*)d->buffer, 
                                          16777216, TRUE,
               FILE_NOTIFY_CHANGE_FILE_NAME  | FILE_NOTIFY_CHANGE_DIR_NAME |
               FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
               FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS | 
               FILE_NOTIFY_CHANGE_CREATION   | FILE_NOTIFY_CHANGE_SECURITY,
                                          NULL,
                                          &d->o->overlapped,
                                          NULL );
        bResultQ = GetQueuedCompletionStatus(d->hDirOPPort, 
                                             &NumBytes, &(d->CompletionKey), 
                                             (LPOVERLAPPED*)(d->o), 1000);

So, now we've called those functions, we then test that they both returned true. big ugly warning if you've got your parameters set up right bResultR always returns true, or so it seems to me. bResultQ however varies depending on whether new data is on the port.

        if ( bResultQ && bResultR )
        {

So here we cast that buffer from ReadDirectoryChangesW and access the info from the struct.

            wprintf(L"\n");
            pInfo = (FILE_NOTIFY_INFORMATION*) d->buffer;
            wprintf(L"File %s", pInfo->FileName);
            wprintf(L" changes %d\n", pInfo->Action);
            memset(d->buffer, 0, 16777216);
        }

Otherwise, and thanks to Tony for this, you can safely ignore WAIT_TIMEOUT errors, but anything else probably means you're in trouble.

        else
        {
            errorcode = GetLastError();

            if ( errorcode == WAIT_TIMEOUT )
            {
                printf("GetQueuedCompletionStatus(): Timeout\n");
            }
            else
            {
                printf("GetQueuedCompletionStatus(): Failed\n");
                printf("Error Code %d\n", errorcode);
            }
            Sleep(500);
        }
    }   

    return 0;
}

And that completes what I think is a working example.

Some notes:

  • I've set the buffer size to be huge. I noticed copying 100 files or so that the buffer ran out of space set to 8192 and missed off an item or two, here and there. So I don't expect this will always pick up everything. My solution would be to say every 100 events, verify the file tree is what you think it is if using this method. An infinitely better solution, however, to constantly enumerating the potentially large tree.
十雾 2024-11-16 22:11:44

注意:要正确捕获 GetQueuedCompletionStatus 中的错误,因为很难确定该函数是否实际返回,应按如下方式完成:

示例:

DWORD dwNumBytes;
ULONG_PTR CompletionKey;
OVERLAPPED* pOverlapped;

//hIOCP is initialized somewhere else in the program
BOOL bOK = GetQueuedCompletionStatus(hIOCP, &dwNumBytes, &CompletionKey, &pOverlapped, 1000);

DWORD dwError = GetLastError();

if(bOK)
{
// Process a successfully completed I/O event.
}
else
{
  if (pOverlapped != NULL)
  {
    // Process a failed completed I/O request
    //dwError contains the reason for failure
  }
  else {
      if (dwError == WAIT_TIMEOUT)
      {
         //Time-out while waiting for completed I/O entry.
      }
      else {
          //Bad call to GetQueuedCompletionStatus
          //dwError contains the reason for the bad call.
      }
}

摘自书中的示例(Windows 通过 C/C++)
请尝试在您的代码中实现此错误处理。

此外,“...调用 GetQueuedCompletionStatus 的线程以后进先出 (LIFO) 方式唤醒。”

重叠结构:

执行异步设备时
I/O,您必须将地址传递给
初始化的OVERLAPPED结构
通过 pOverlapped 参数。
在这种情况下“重叠”一词
意味着花在表演上的时间
I/O 请求与您的时间重叠
线程用于执行其他任务。

讲的是调用ReadFileWriteFile时的参数,就像上面的注释一样,需要初始化这个结构体。

它看起来如下:

typedef struct _OVERLAPPED {
  ULONG_PTR Internal;
  ULONG_PTR InternalHigh;
  union {
    struct {
      DWORD Offset;
      DWORD OffsetHigh;
    };
    PVOID  Pointer;
  };
  HANDLE    hEvent;
} OVERLAPPED, *LPOVERLAPPED;

注意:您正在将指向结构的指针传递给 CreateIoCompletionPort 函数的 dwCompletionKey 参数。在我正在查看的参考文献中,他们只是将一个常量(#define CK_FILE 1)传递给它。它确实说你可以传递任何你喜欢的内容,因为操作系统不在乎。只是想指出这一点。

Note: To catch errors from GetQueuedCompletionStatus properly, as it is difficult to determine that this function actually returned, should be done as follows:

EXAMPLE:

DWORD dwNumBytes;
ULONG_PTR CompletionKey;
OVERLAPPED* pOverlapped;

//hIOCP is initialized somewhere else in the program
BOOL bOK = GetQueuedCompletionStatus(hIOCP, &dwNumBytes, &CompletionKey, &pOverlapped, 1000);

DWORD dwError = GetLastError();

if(bOK)
{
// Process a successfully completed I/O event.
}
else
{
  if (pOverlapped != NULL)
  {
    // Process a failed completed I/O request
    //dwError contains the reason for failure
  }
  else {
      if (dwError == WAIT_TIMEOUT)
      {
         //Time-out while waiting for completed I/O entry.
      }
      else {
          //Bad call to GetQueuedCompletionStatus
          //dwError contains the reason for the bad call.
      }
}

Example taken from the book (Windows via C/C++)
Please try to implement this error handling in your code.

Also "... threads that call GetQueuedCompletionStatus are awakened in a last-in first-out (LIFO) fashion."

OVERLAPPED Structure:

When performing asynchronous device
I/O, you must pass the address to an
initialized OVERLAPPED structure
via the pOverlapped parameter.
The word "overlapped" in this context
means that the time spent performing
I/O requests overlaps the time your
thread spends performing other tasks.

It's talking about the parameter when you call ReadFile or WriteFile, just as a note to the above, which requires this structure to be initialized.

It looks as follows:

typedef struct _OVERLAPPED {
  ULONG_PTR Internal;
  ULONG_PTR InternalHigh;
  union {
    struct {
      DWORD Offset;
      DWORD OffsetHigh;
    };
    PVOID  Pointer;
  };
  HANDLE    hEvent;
} OVERLAPPED, *LPOVERLAPPED;

NOTE: You are passing a pointer to a struct to your dwCompletionKey parameter of your CreateIoCompletionPort function. In the reference I am looking at they merely pass a constant (#define CK_FILE 1) to this. It does say you can pass whatever you like, as the OS doesn't care. Just wanted to point it out however.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文