我试图在循环中异步使用ReadDirectoryChangesW 。
以下代码段说明了我要实现的目标:
DWORD example()
{
DWORD error = 0;
OVERLAPPED ovl = { 0 };
ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == ovl.hEvent) return ::GetLastError();
char buffer[1024];
while(1)
{
process_list_of_existing_files();
error = ::ReadDirectoryChangesW(
m_hDirectory, // I have added FILE_FLAG_OVERLAPPED in CreateFile
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
// we have new files, append them to the list
if(error) append_new_files_to_the_list(buffer);
// just continue with the loop
else if(::GetLastError() == ERROR_IO_PENDING) continue;
// RDCW error, this is critical -> exit
else return ::GetLastError();
}
}
我不知道当ReadDirectoryChangesW
返回FALSE
且GetLastError()
代码为ERROR_IO_PENDING
时,如何处理此案例。
在这种情况下,我应该继续循环并继续循环,直到ReadDirectoryChangesW
返回buffer
我可以处理。
我尝试使用WaitForSingleObject(ovl.hEvent, 1000)
,但崩溃时遇到错误1450 ERROR_NO_SYSTEM_RESOURCES
。下面是重现此行为的MVCE:
#include <iostream>
#include <Windows.h>
DWORD processDirectoryChanges(const char *buffer)
{
DWORD offset = 0;
char fileName[MAX_PATH] = "";
FILE_NOTIFY_INFORMATION *fni = NULL;
do
{
fni = (FILE_NOTIFY_INFORMATION*)(&buffer[offset]);
// since we do not use UNICODE,
// we must convert fni->FileName from UNICODE to multibyte
int ret = ::WideCharToMultiByte(CP_ACP, 0, fni->FileName,
fni->FileNameLength / sizeof(WCHAR),
fileName, sizeof(fileName), NULL, NULL);
switch (fni->Action)
{
case FILE_ACTION_ADDED:
{
std::cout << fileName << std::endl;
}
break;
default:
break;
}
::memset(fileName, '\0', sizeof(fileName));
offset += fni->NextEntryOffset;
} while (fni->NextEntryOffset != 0);
return 0;
}
int main()
{
HANDLE hDir = ::CreateFile("C:\\Users\\nenad.smiljkovic\\Desktop\\test",
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
if (INVALID_HANDLE_VALUE == hDir) return ::GetLastError();
OVERLAPPED ovl = { 0 };
ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == ovl.hEvent) return ::GetLastError();
DWORD error = 0, br;
char buffer[1024];
while (1)
{
error = ::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
if (0 == error)
{
error = ::GetLastError();
if (ERROR_IO_PENDING != error)
{
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
}
error = ::WaitForSingleObject(ovl.hEvent, 0);
switch (error)
{
case WAIT_TIMEOUT:
break;
case WAIT_OBJECT_0:
{
error = processDirectoryChanges(buffer);
if (error > 0)
{
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
if (0 == ::ResetEvent(ovl.hEvent))
{
error = ::GetLastError();
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
}
break;
default:
error = ::GetLastError();
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
break;
}
}
return 0;
}
阅读文档,似乎我需要将GetOverlappedResult的最后一个参数设置为FALSE
,但我不知道如何正确使用此API。
由于MVCE很好地说明了我要做的事情(打印新添加文件的名称),你能告诉我在while
循环中必须修复哪些内容才能使它工作? / p>
同样,重点是在循环中异步使用ReadDirectoryChangesW
,如简介中的代码段所示。
答案 0 :(得分:3)
程序的基本结构或多或少看起来不错,您只是错误地使用异步I / O调用。每当没有新文件时,事件句柄上的等待会立即超时,这很好,但是您会发出一个全新的I / O请求,但不是。
这就是为什么你的系统资源不足的原因;您无需等待其中任何一个完成即可完全发出I / O请求。您应该只在现有请求完成后发出新请求。
(另外,你应该调用GetOverlappedResult来检查I / O是否成功。)
所以你的循环看起来应该更像这样:
::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
while (1)
{
DWORD result = ::WaitForSingleObject(ovl.hEvent, 0);
switch (result)
{
case WAIT_TIMEOUT:
processBackgroundTasks();
break;
case WAIT_OBJECT_0:
::GetOverlappedResult(hDir, &ovl, &dw, FALSE);
processDirectoryChanges(buffer);
::ResetEvent(ovl.hEvent);
::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
break;
}
}
注意:
为简单起见,错误处理已被省略;我没有进行任何测试或检查您的代码是否有任何其他问题。
如果可能没有任何后台任务要执行,您应该测试该情况并将超时设置为INFINITE而不是0,否则您将会旋转。
我只希望显示使其工作所需的最小变化,但调用WaitForSingleObject后跟GetOverlappedResult是多余的;对GetOverlappedResult的单次调用都可以检查I / O是否完整,如果是,则检索结果。
根据要求,修改后的版本仅使用GetOverlappedResult并且具有最小的错误检查。我还添加了一个例子,说明如何处理你没有工作的情况;如果您对文件进行的任何处理确实真的一直在运行,那么您就不需要这样了。
::ResetEvent(ovl.hEvent);
if (!::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL))
{
error = GetLastError();
if (error != ERROR_IO_PENDING) fail();
}
while (1)
{
BOOL wait;
result = process_list_of_existing_files();
if (result == MORE_WORK_PENDING)
{
wait = FALSE;
}
else if (result == NO_MORE_WORK_PENDING)
{
wait = TRUE;
}
if (!::GetOverlappedResult(hDir, &ovl, &dw, wait))
{
error = GetLastError();
if (error == ERROR_IO_INCOMPLETE) continue;
fail();
}
processDirectoryChanges(buffer);
::ResetEvent(ovl.hEvent);
if (!::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL))
{
error = GetLastError();
if (error != ERROR_IO_PENDING) fail();
}
}
答案 1 :(得分:1)
间接使用IOCP的变体
BindIoCompletionCallback
(RtlSetIoCompletionCallback
)
目录句柄,用于设置回调DoRead()
例程,我们第一次从主线程调用
然后从回调DoRead()
电话之前,{li}都在ReadDirectoryChangesW
AddRef();
因为我们将引用(跨越OVERLAPPED
)传递给我们
struct to kernel
DoRead()
,与APC变体不同,他不需要等待处于警戒状态processDirectoryChanges
)
继续间谍 - 调用DoRead()
,最后调用Release()ReadDirectoryChangesW
的{{1}}失败(结果将无法回调) - 我们需要直接回拨
错误代码==================================
DoRead()