异步ReadDirectoryChangesW - GetQueuedCompletionStatus总是超时

10 c winapi asynchronous readdirectorychangesw

正如它听起来一样,我试图ReadDirectoryChangesW与IO完成异步并且它不工作,特别是,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;
Run Code Online (Sandbox Code Playgroud)

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

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));
Run Code Online (Sandbox Code Playgroud)

然后我创建我的目录句柄和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); 
Run Code Online (Sandbox Code Playgroud)

然后我通过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);
}
Run Code Online (Sandbox Code Playgroud)

所以我把它设置为运行,我快乐地获得超时(258错误),因为目录没有改变.但是,即使我改变目录,我仍然会收到错误消息; 换句话说,这些改动没有得到回应.这让我相信我的设置不正确.

有任何想法吗?

注意事项:

  • 具有讽刺意味的是,这最终将通过pywin32转换为Python.但是,在我理解这应该如何在C中起作用之前,我不会去那里.
  • 同步ReadDirectoryChangesW不是一种选择.如果没有触发任何事件,我需要启用的线程超时,以便它可以检查它是否仍然在运行.
  • 我特意写的是C语言,而不是C++.
  • FindFirstChangeNotification 等也不是一个选项 - 我不想继续比较目录列表来找出已经改变的内容.

其他说明:

  • 目录存在,句柄不是NULL.同样对于comport句柄.
  • 一切都被传递给线程

我从代码项目看了一下CDirectoryChangeWatcher,但是除了使用C++和更多的线程之外,我看不出我在做什么不同.如果我遗失了一些东西,请随意指出!

如果有帮助,输出基本上是重复的,无论我改变了多少目录.

GetQueuedCompletionStatus(): Failed, Error Code 258
Run Code Online (Sandbox Code Playgroud)

小智 6

我发现发布代码墙通常被认为是可怕的,但这是我如何工作:

新结构:

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;
Run Code Online (Sandbox Code Playgroud)

分配方法:

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);
}
Run Code Online (Sandbox Code Playgroud)

重要的东西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);  
Run Code Online (Sandbox Code Playgroud)

最后我的等待线程.关键是:我没有传递重叠的结构.我传递的结构包含一个OVERLAPPED加上相当数量的wchar_t基础存储.由于我不完全理解的原因,这是有效的.编辑看到这个答案.我相信这里的数据区域充当重叠缓冲区.

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.
Run Code Online (Sandbox Code Playgroud)

然后我们进入主线程本身.试验和错误表明您应该同时调用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);
Run Code Online (Sandbox Code Playgroud)

所以,现在我们调用了那些函数,然后我们测试它们都返回true.如果你的参数设置正确,那么大丑陋的警告bResultR总是会返回true,或者在我看来.bResultQ但是根据端口上是否有新数据而有所不同.

        if ( bResultQ && bResultR )
        {
Run Code Online (Sandbox Code Playgroud)

所以这里我们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);
        }
Run Code Online (Sandbox Code Playgroud)

否则,感谢Tony,你可以放心地忽略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;
}
Run Code Online (Sandbox Code Playgroud)

这完成了我认为的一个有效的例子.

一些说明:

  • 我已经将缓冲区大小设置为巨大的.我注意到复制了100个文件,或者说缓冲区空间设置为8192并且错过了一两个项目,这里和那里.所以我不指望这总能拿走一切.我的解决方案是说每100个事件,验证文件树是否是您认为使用此方法时的情况.然而,一个无限更好的解决方案是不断枚举潜在的大树.