问题描述
我正在异步调用ReadDirectoryChangesW
来监视后台线程中的目录更改.
I am calling ReadDirectoryChangesW
asynchronously to monitor directory changes in a background thread.
这是如何打开目录(basePath
)并启动读取"线程的方法:
This how the directory (basePath
) is opened and the "reading" thread is started:
m_hDIR = CreateFileW(
basePath,
FILE_LIST_DIRECTORY | GENERIC_READ,
FILE_SHARE_WRITE | FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
if (m_hDIR == INVALID_HANDLE_VALUE)
throw CrException(CrWin32ErrorString());
//Start reading changes in background thread
m_Callback = std::move(a_Callback);
m_Reading = true;
m_ReadThread = std::thread(&CrDirectoryWatcher::StartRead, this);
这是StartRead()
:(注意:m_Reading
是atomic<bool>
)
void StartRead()
{
DWORD dwBytes = 0;
FILE_NOTIFY_INFORMATION fni{0};
OVERLAPPED o{0};
//Be sure to set the hEvent member of the OVERLAPPED structure to a unique event.
o.hEvent = CreateEvent(0, 0, 0, 0);
while(m_Reading)
{
if (!ReadDirectoryChangesW(m_hDIR,
&fni, sizeof(fni),
TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE,
&dwBytes, &o, NULL))
{
CrAssert(0, CrWin32ErrorString());
}
if (!GetOverlappedResult(m_hDIR, &o, &dwBytes, FALSE))
CrAssert(0, CrWin32ErrorString());
if (fni.Action != 0)
{
std::wstring fileName(fni.FileName, fni.FileNameLength);
m_Callback(fileName);
fni.Action = 0;
}
}
}
基本上,我正在轮询"每帧的新更改.现在,当我呼叫GetOverlappedResult()
时,它将失败并产生以下错误:
Basically, I am "polling" for new changes every frame.Now when I call GetOverlappedResult()
it fails and yields the following error:
我错过了什么吗? ReadDirectoryChangesW
是否应被称为每个滴答"?还是只是在检测到新更改时?
Am I missing something? Is ReadDirectoryChangesW
meant to be called every "tick"? Or just when new changes were detected?
注意:当我省略OVERLAPPED
结构(和GetOverlappedResult
)时,它可以工作,但是会阻塞线程,直到读取更改为止.这会阻止我的应用程序正常终止. (即我无法加入线程)
Note: When I leave out the OVERLAPPED
struct (and GetOverlappedResult
) it works, but blocks the thread until changes were read. This prevents my application to properly terminate. (i.e. I can't join the thread)
推荐答案
调用GetOverlappedResult()
时,如果将bWait
参数设置为FALSE并且I/O操作尚未完成,则GetOverlappedResult()
失败,并显示ERROR_IO_INCOMPLETE
错误代码:
When calling GetOverlappedResult()
, if you set the bWait
parameter to FALSE and the I/O operation hasn't completed yet, GetOverlappedResult()
fails with an ERROR_IO_INCOMPLETE
error code:
这不是致命错误,因此只需忽略该错误并继续.
That is not a fatal error, so just ignore that error and move on.
是的,请确保在GetOverlappedResult()
报告上一个I/O操作首先完成之前,不要再次调用ReadDirectoryChangesW()
.
And yes, make sure you don't call ReadDirectoryChangesW()
again until GetOverlappedResult()
has reported the previous I/O operation has completed first.
现在,这样说来,您的代码还有另一个问题.您的线程在堆栈上分配了一个FILE_NOTIFY_INFORMATION
实例.如果您查看FILE_NOTIFY_INFORMATION
的定义,则其FileName
字段的长度是可变的:
Now, with that said, there is another problem with your code. Your thread is allocating a single FILE_NOTIFY_INFORMATION
instance on the stack. If you look at the definition of FILE_NOTIFY_INFORMATION
, its FileName
field is variable-length:
typedef struct _FILE_NOTIFY_INFORMATION {
DWORD NextEntryOffset;
DWORD Action;
DWORD FileNameLength;
WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;
这意味着静态分配FILE_NOTIFY_INFORMATION
的空间将太小,并且dwBytes
几乎始终为0,因为ReadDirectoryChangesW()
无法将完整的FILE_NOTIFY_INFORMATION
返回给您(除非长度正好为1个字符):
Which means allocating a FILE_NOTIFY_INFORMATION
statically is going to be too small, and dwBytes
will almost always be 0 since ReadDirectoryChangesW()
won't be able to return a full FILE_NOTIFY_INFORMATION
to you (unless the FileName
is exactly 1 character in length):
因此,您需要动态分配一个大字节缓冲区来接收FILE_NOTIFY_INFORMATION
数据,然后只要GetOverlappedResult()
报告数据可用,就可以遍历该缓冲区.
So, you need to dynamically allocate a large byte buffer for receiving FILE_NOTIFY_INFORMATION
data, and then you can walk that buffer whenever GetOverlappedResult()
reports that data is available.
您的线程应该看起来像这样:
Your thread should look something more like this:
void StartRead()
{
DWORD dwBytes = 0;
std::vector<BYTE> buffer(1024*64);
OVERLAPPED o{0};
bool bPending = false;
//Be sure to set the hEvent member of the OVERLAPPED structure to a unique event.
o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!o.hEvent) {
CrAssert(0, CrWin32ErrorString());
}
while (m_Reading)
{
bPending = ReadDirectoryChangesW(m_hDIR,
&buffer[0], buffer.size(),
TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE,
&dwBytes, &o, NULL);
if (!bPending)
{
CrAssert(0, CrWin32ErrorString());
}
while (m_Reading)
{
if (GetOverlappedResult(m_hDIR, &o, &dwBytes, FALSE))
{
bPending = false;
if (dwBytes != 0)
{
FILE_NOTIFY_INFORMATION *fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(&buffer[0]);
do
{
if (fni->Action != 0)
{
std::wstring fileName(fni->FileName, fni->FileNameLength);
m_Callback(fileName);
}
if (fni->NextEntryOffset == 0)
break;
fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<BYTE*>(fni) + fni->NextEntryOffset);
}
while (true);
}
break;
}
if (GetLastError() != ERROR_IO_INCOMPLETE) {
CrAssert(0, CrWin32ErrorString());
}
Sleep(10);
}
if (bPending)
{
CancelIo(m_hDIR);
GetOverlappedResult(m_hDIR, &o, &dwBytes, TRUE);
}
}
CloseHandle(o.hEvent);
}
在不定期轮询I/O状态的情况下实现此目标的另一种方法是摆脱m_Reading
并使用可等待事件.让操作系统在应调用GetOverlappedResult()
或终止时向线程发出信号,这样它就可以在不忙于做某事的其余时间中休眠:
An alternative way to implement this without polling the I/O status regularly would be to get rid of m_Reading
and use a waitable event instead. Let the OS signal the thread when it should call GetOverlappedResult()
or terminate, that way it can sleep the rest of the time it is not busy doing something:
m_hDIR = CreateFileW(
basePath,
FILE_LIST_DIRECTORY | GENERIC_READ,
FILE_SHARE_WRITE | FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
if (m_hDIR == INVALID_HANDLE_VALUE)
throw CrException(CrWin32ErrorString());
m_TermEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!m_TermEvent)
throw CrException(CrWin32ErrorString());
//Start reading changes in background thread
m_Callback = std::move(a_Callback);
m_ReadThread = std::thread(&CrDirectoryWatcher::StartRead, this);
...
SetEvent(m_TermEvent);
m_ReadThread.join();
void StartRead()
{
DWORD dwBytes = 0;
std::vector<BYTE> buffer(1024*64);
OVERLAPPED o{0};
bool bPending = false, bKeepRunning = true;
//Be sure to set the hEvent member of the OVERLAPPED structure to a unique event.
o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!o.hEvent) {
CrAssert(0, CrWin32ErrorString());
}
HANDLE h[2] = {o.hEvent, h_TermEvent};
do
{
bPending = ReadDirectoryChangesW(m_hDIR,
&buffer[0], buffer.size(),
TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE,
&dwBytes, &o, NULL);
if (!bPending)
{
CrAssert(0, CrWin32ErrorString());
}
switch (WaitForMultipleObjects(2, h, FALSE, INFINITE))
{
case WAIT_OBJECT_0:
{
if (!GetOverlappedResult(m_hDIR, &o, &dwBytes, TRUE)) {
CrAssert(0, CrWin32ErrorString());
}
bPending = false;
if (dwBytes == 0)
break;
FILE_NOTIFY_INFORMATION *fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(&buffer[0]);
do
{
if (fni->Action != 0)
{
std::wstring fileName(fni->FileName, fni->FileNameLength);
m_Callback(fileName);
}
if (fni->NextEntryOffset == 0)
break;
fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<BYTE*>(fni) + fni->NextEntryOffset);
}
while (true);
break;
}
case WAIT_OBJECT_0+1:
bKeepRunning = false;
break;
case WAIT_FAILED:
CrAssert(0, CrWin32ErrorString());
break;
}
}
while (bKeepRunning);
if (bPending)
{
CancelIo(m_hDIR);
GetOverlappedResult(m_hDIR, &o, &dwBytes, TRUE);
}
CloseHandle(o.hEvent);
}
这篇关于ReadDirectoryChangesW和GetOverlappedResult的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!