QueueUserAPC功能不工作,随机报告错误31



下面的代码使用QueueUserAPC函数向调度线程添加命令,以便同步控制台输出。

#include <Windows.h>
#include <iostream>
constexpr auto fenceName = L"GlobalFence";
constexpr auto dispatchCloser = L"GlobalDispatchStop";
constexpr int threadCount = 5;
DWORD WINAPI pure(LPVOID lpThreadParameter)
{
const HANDLE dispatchCloseEvent = OpenEventW(EVENT_ALL_ACCESS, FALSE, dispatchCloser);
while(WaitForSingleObjectEx(dispatchCloseEvent, INFINITE, TRUE) != WAIT_IO_COMPLETION)continue;
return 0;
}
HANDLE dispatcher;
int main()
{
const HANDLE dispatchCloseEvent = CreateEventW(nullptr, TRUE, FALSE, dispatchCloser);
dispatcher = CreateThread(NULL, 1024, &pure, 0, 0, NULL);
const HANDLE g_FenceEvent = CreateEventW(nullptr, TRUE, FALSE, fenceName);
HANDLE threads[threadCount];
for (int i = 0; i < threadCount; i++)
{
threads[i] = CreateThread(NULL, 1024*1024, 
[](LPVOID) -> DWORD 
{
DWORD d = QueueUserAPC(([](ULONG_PTR) {std::cout << "thread openedn"; }), dispatcher, NULL);
if(d == 0)std::cout << GetLastError() << std::endl;
HANDLE a = OpenEventW(EVENT_ALL_ACCESS, FALSE, fenceName);
WaitForSingleObject(a, INFINITE);
d = QueueUserAPC([](ULONG_PTR) {std::cout << "thread releasedn"; }, dispatcher, NULL);
if (d == 0)std::cout << GetLastError() << std::endl;//often reports error 31
return 0;
}, 
0, 0, NULL);
}
Beep(300, 300);//the length of the delay effects the behavior, somehow.
SetEvent(g_FenceEvent);
SetEvent(dispatchCloseEvent);
WaitForMultipleObjects(threadCount, threads, TRUE, INFINITE);
WaitForSingleObject(dispatcher, INFINITE);
SetEvent(dispatchCloseEvent);
for (int i = 0; i < threadCount; i++)
CloseHandle(threads[i]);
CloseHandle(g_FenceEvent);
CloseHandle(dispatchCloseEvent);
}

代码的正确执行率约为40%。有时(虽然这种情况比较少见),"线程打开了"。文本不会被写入控制台正确的次数,但没有错误报告从getLastError()

pure()中的循环收到第一个APC通知时,循环中断,pure()退出,线程终止。

错误31是ERROR_GEN_FAILURE,根据QueueUserAPC()文档:

当线程处于被终止的过程中,调用QueueUserAPC来添加线程的APC队列将失败,返回(31)ERROR_GEN_FAILURE。

如果你希望调度线程处理多个APC通知,它需要保持运行。您的意思是在循环条件中使用==而不是!=:

while (WaitForSingleObjectEx(dispatchCloseEvent, INFINITE, TRUE) == WAIT_IO_COMPLETION) continue;

这样,如果等待由于排队的APC而退出,循环将返回等待下一个APC。循环将中断,退出pure()终止线程,只有当它收到返回值而不是APC通知时,例如当关闭事件发出信号时WAIT_OBJECT_0

我看到的另一个问题是,你过早地给dispatchCloseEvent发信号,所以dispatcher线程可以停止运行,而fenced线程仍然试图将apc排队到它。这就是为什么在每个隔离线程中对QueueUserAPC()的第二次调用随机失败的原因。您需要先等待所有被隔离的线程完成,然后向调度程序发出停止运行的信号。

SetEvent(g_FenceEvent);
WaitForMultipleObjects(threadCount, threads, TRUE, INFINITE);
SetEvent(dispatchCloseEvent); // <-- move here!
WaitForSingleObject(dispatcher, INFINITE);

同样,你所有的线程都泄漏了OpenEventW()打开的HANDLE。你需要调用CloseHandle(),根据OpenEventW()文档:

使用CloseHandle函数关闭句柄。当进程终止时,系统自动关闭句柄。事件对象在其最后一个句柄关闭时被销毁。

就此而言,您根本不需要OpenEventW()。您可以通过LPVOID参数将现有的HANDLEmain()传递给每个线程:

DWORD WINAPI pure(LPVOID lpThreadParameter)
{
HANDLE dispatchCloseEvent = (HANDLE) lpThreadParameter;
...
return 0;
}
CreateThread(..., &pure, dispatchCloseEvent, ...);
CreateThread(...,
[](LPVOID param) -> DWORD 
{
HANDLE a = (HANDLE) param;
...
}, 
g_fenceEvent, ...);

或者直接使用全局变量。

无论哪种方式,消除OpenEventW()后,在调用CreateEventW()时不再需要为事件分配名称,从而不再将它们暴露于外部代码的潜在干扰。


此外,您也没有关闭dispatcher线程的HANDLE,就像您正在关闭围栏线程的HANDLEs一样。