【问题标题】:Asynchronous ReadDirectoryChangesW - GetQueuedCompletionStatus always times out异步 ReadDirectoryChangesW - GetQueuedCompletionStatus 总是超时
【发布时间】:2011-05-27 10:29:58
【问题描述】:

正如听起来一样,我正在尝试使用 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。对于 comport 句柄也是如此。
  • 一切都被传递给线程

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

输出,如果有帮助的话,基本上是重复的,不管我如何改变有问题的目录。

GetQueuedCompletionStatus(): Failed, Error Code 258

【问题讨论】:

  • 让它正常工作并非易事(委婉地说)。如果您还没有,我建议您阅读 Jim Beveridge 的博客:qualapps.blogspot.com/2010/05/…。警告:他的示例代码是用 C++ 编写的,但至少比大多数代码要好。警告2:我有理由确定,无论您做什么,ReadDirectoryChangesW 都不会真正完全正确地工作。我通常建议改用更改日志。 msdn.microsoft.com/en-us/library/aa363798.aspx。它们也不是微不足道的,但至少它们确实有效。
  • @Jeffy 谢谢,我查看了他的来源,但他没有(据我所知)使用 GetQueue... 或 CreateIo... 他的自述文件暗示了比实际更多的文件实际上在那里,所以我认为他已经删除了它们。我慢慢开始意识到这一点......不幸的是,更改期刊需要我们可能没有的管理员权限。我总是可以启动一个 Windows 服务来处理我想的。

标签: c winapi asynchronous readdirectorychangesw


【解决方案1】:

我意识到发布代码墙通常被认为是可怕的,但我是这样工作的:

新结构:

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 的存储的结构。由于我不完全理解的原因,这是可行的。 编辑this answer。我相信这里的数据区域充当了重叠的缓冲区。

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

否则,thanks to Tony for this,您可以放心地忽略 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 个事件,如果使用此方法,验证文件树是您认为的内容。然而,对于不断枚举潜在的大树来说,这是一个无限好的解决方案。

【讨论】:

  • 嗯,很有趣。绝对是一个冒险这个 IO 的东西。 :) 感谢发帖。
  • @Ninefingers:嗨,我正在做类似的项目(但在 c++ 中),我的问题是将监视目录线程与我的应用程序线程分开,如果你不介意打电话给我的话WaitingThread 我将它附加到您的主代码中,但我仍然遇到同样的问题,提前致谢。
  • @Antony 请解释一下: malloc(16777216*sizeof(wchar_t)) 我猜 16777216 = 8192 * 2048 => 这是什么 2048?谢谢!
  • 嘿安东尼,C# 中是否有上述代码的示例?
  • @NadeemUllah 你好,我不知道 - 但是我对 C# 不太了解,所以很可能。
【解决方案2】:

注意:要正确捕获来自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)传递给它。它确实说你可以通过任何你喜欢的东西,因为操作系统不在乎。只是想指出这一点。

【讨论】:

  • 非常感谢,我现在知道预计可能会失败,实际上可能包含数据......!我已经将我的 Overlapped 结构修改为 ZeroMemory'd,我将 CompletionKey 指向一个常量值等。不过,每次我得到一个 null 重叠结构时,返回错误值和超时错误代码。
  • 来自 MSDN:“如果调用 GetQueuedCompletionStatus 失败,因为与其关联的完成端口句柄在调用未完成时关闭,函数返回 FALSE,*lpOverlapped 将为 NULL,GetLastError 将返回 ERROR_ABANDONED_WAIT_0 。”
  • NumBytes 值呢,返回时是否已填满?
  • 我可能让它工作。我错过了将 CreateIo.. 完成键更改为常量。现在我可以看到缓冲区中的数据,这只是正确调用 ReadDirectoryChangesW 以将其取出的情况。
猜你喜欢
  • 1970-01-01
  • 2021-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多