如何在WaitForSingleObject()上等待stdin的CancelSynchronousIo()?

Windows 10上,我正在等待来自控制台的input

WaitForSingleObject ( GetStdHandle(STD_INPUT_HANDLE), ... )

并使用CancelSynchronousIo()取消等待。

但取消什么都不做(返回0GetLastError()ERROR_NOT_FOUND )。

任何想法我可能做错了什么?
我应该能够取消这个等待在标准input新的input?

(我真的想要用GetFileType()FILE_TYPE_CHAR任何HANDLE来做到这一点,而不仅仅是stdin,但stdin肯定是最重要的用例,也是最简单的testing用例)。


相关讨论我发现:

  • stdin上的同步ReadFile()不能被CancelSynchronousIo()
  • win32:如何停止ReadFile(stdin | pipe)

但不幸的是,他们只讨论ReadFile() ,而不是WaitForSingleObject() 。 我也尝试过WaitForMultipleObjects() (只有一个对象在数组中),同样的问题。

背景:我正在尝试改进GHC Haskell编译器运行时的input处理 。

控制台I / O很难异步使用,它根本不是为它设计的。 通过STDIN,STDOUT和STDERR查看IO完成端口(IOCP)和异步I / O,了解一些可能的解决方法。

如果这不是您的选择,那么您将不得不:

  1. 在短暂超时的循环中使用WaitForSingleObject() 。 创建一个标志变量,循环可以在每次迭代中查看,以便在标志被设置时中断循环。

  2. 使用WaitForMutipleObjects() ,给它2个HANDLE等待 – 一个用于控制台(或其他),一个用于CreateEvent()的事件对象。 然后当你想打破这个等待时,你可以用SetEvent()来发信号。 WaitForMutipleObjects()的返回值会告诉你哪个HANDLE被发信号。

CancelSynchronousIo取消指定线程发出的I / O操作。 更具体地说,它通过调用IoCancelIrp来取消与指定线程关联的IRP包。 如果使用未记录的NtCancelSynchronousIoFileNtCancelSynchronousIoFile内部调用IoRequestToCancel = 0 ),我们可以更加选择性地取消只使用指定IoRequestToCancel I / O请求(系统检查Irp->UserIosb == IoRequestToCancel并仅取消此请求)

WaitForSingleObject不是I / O请求 。 这个调用不会创建任何可以取消的IRP 。 所以 – 没有办法做到这一点。

但是,如果使用WaitForSingleObjectEx并将bAlertable设置为TRUE ,则可以通过使用QueueUserAPCbAlertable队列apc 等待线程。 如果使用NtWaitForSingleObject而不是WaitForSingleObjectEx我们也可以通过使用未记录的调用NtAlertThread来提醒线程。 在这种情况下, NtWaitForSingleObject将与STATUS_ALERTED (请注意, WaitForSingleObjectEx在内部调用NtWaitForSingleObjectSTATUS_ALERTED进行特殊检查并且在这种情况下再次运行NtWaitForSingleObject – 因此我们不能通过调用NtAlertThread来打破WaitForSingleObjectEx ,而是NtWaitForSingleObject将被打破)。

所以如果你需要中断等待std输入 – 创建额外的线程,必须调用不CancelSynchronousIo (这没有意义),但QueueUserAPCNtAlertThread (只有当您使用NtWaitForSingleObject的等待)。 并且输入线程必须等待处于可警告状态。 所以演示代码可以像这样:

 extern "C" NTSYSCALLAPI NTSTATUS NTAPI NtAlertThread(HANDLE ThreadHandle); VOID NTAPI OnApc(ULONG_PTR Parameter) { DbgPrint("OnApc(%p)\n", Parameter); } DWORD CALLBACK BreakWaitThread(HANDLE hThread) { switch (LONG status = MessageBoxW(0, L"Use Apc(yes) or Alert(No) ?", L"BreakWaitThread", MB_ICONQUESTION|MB_YESNOCANCEL|MB_DEFBUTTON3)) { case IDYES: if (!QueueUserAPC(OnApc, hThread, 0)) { DbgPrint("QueueUserAPC=%u\n", GetLastError()); } break; case IDNO: if (0 > (status = NtAlertThread(hThread))) { DbgPrint("AlertThread=%x\n", status); } break; case IDCANCEL: DbgPrint("canceled\n"); break; default: DbgPrint("MessageBox=%x\n", status); } CloseHandle(hThread); return 0; } void ConsoleLoop(HANDLE hStdIn) { ULONG NumberOfEvents, NumberOfEventsRead, n; INPUT_RECORD buf[8], *p; for (;;) { switch (ZwWaitForSingleObject(hStdIn, TRUE, 0)) //switch (WaitForSingleObjectEx(hStdIn, INFINITE, TRUE)) { case WAIT_OBJECT_0: while (GetNumberOfConsoleInputEvents(hStdIn, &NumberOfEvents) && NumberOfEvents) { do { NumberOfEventsRead = min(RTL_NUMBER_OF(buf), NumberOfEvents); if (ReadConsoleInput(hStdIn, buf, NumberOfEventsRead, &NumberOfEventsRead) && NumberOfEventsRead) { n = NumberOfEventsRead; p = buf; do { if (p->EventType == KEY_EVENT) { DbgPrint("%u(%u) %C %x %x %x\n", p->Event.KeyEvent.bKeyDown, p->Event.KeyEvent.wRepeatCount, p->Event.KeyEvent.uChar.UnicodeChar, p->Event.KeyEvent.wVirtualKeyCode, p->Event.KeyEvent.wVirtualScanCode, p->Event.KeyEvent.dwControlKeyState); if (VK_OEM_PERIOD == p->Event.KeyEvent.wVirtualKeyCode) { return ;//if user type '.' return for demo } } } while (p++, --n); } else { FlushConsoleInputBuffer(hStdIn); break; } } while (NumberOfEvents -= NumberOfEventsRead); } continue; case STATUS_USER_APC: DbgPrint("\nUSER_APC\n"); return; case STATUS_ALERTED: DbgPrint("\nALERTED\n"); return; case WAIT_FAILED : DbgPrint("\nWAIT_FAILED=%u\n", GetLastError()); return; default: __debugbreak(); return; } } } void SimpleDemo() { if (HANDLE hCurrentThread = OpenThread(THREAD_ALERT|THREAD_SET_CONTEXT , FALSE, GetCurrentThreadId())) { ULONG dwThreadId; HANDLE hThread = CreateThread(0, 0, BreakWaitThread, hCurrentThread, 0, &dwThreadId); if (hThread) { ConsoleLoop(GetStdHandle(STD_INPUT_HANDLE)); PostThreadMessage(dwThreadId, WM_QUIT, 0, 0); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); } else { CloseHandle(hCurrentThread); } } }