异步过程调用

异步 过程调用 (APC) 是在特定线程的上下文中异步执行的函数。 当 APC 排队到线程时,系统会发出软件中断。 下次计划线程时,它将运行 APC 函数。 系统生成的 APC 称为 内核模式 APC。 应用程序生成的 APC 称为 用户模式 APC。 线程必须处于可警报状态才能运行用户模式 APC。

每个线程都有自己的 APC 队列。 应用程序通过调用 QueueUserAPC 函数将 APC 排队到线程。 调用线程在对 QueueUserAPC 的调用中指定 APC 函数的地址。 APC 的队列是线程调用 APC 函数的请求。

当用户模式 APC 排队时,它排队的线程不会定向到调用 APC 函数,除非它处于可警报状态。 线程在调用 SleepExSignalObjectAndWaitMsgWaitForMultipleObjectsExWaitForMultipleObjectsExWaitForSingleObjectEx 函数时进入可警报状态。 如果在 APC 排队之前满足等待,则线程不再处于可警报等待状态,因此不会执行 APC 函数。 但是,APC 仍处于排队状态,因此当线程调用另一个可警报等待函数时,将执行 APC 函数。

ReadFileExSetWaitableTimerSetWaitableTimerExWriteFileEx 函数使用 APC 作为完成通知回调机制来实现。

如果使用 线程池,请注意,APC 不与其他信号机制一样工作,因为系统控制线程池线程的生存期,因此线程有可能在传递通知之前终止。 不要使用基于 APC 的信号机制(如 SetWaitableTimer 或 SetWaitableTimerExpfnCompletionRoutine 参数),而是使用可等待的对象,例如使用 CreateThreadpoolTimer 创建的计时器。 对于 I/O,请使用使用 CreateThreadpoolIo 创建的 I/O 完成对象或基于 hEvent的 OVERLAPPED 结构,其中事件可以传递给 SetThreadpoolWait 函数。

同步内部

发出 I/O 请求时,将分配一个结构来表示请求。 此结构称为 I/O 请求数据包 (IRP) 。 使用同步 I/O 时,线程生成 IRP,将其发送到设备堆栈,并在内核中等待 IRP 完成。 使用异步 I/O,线程生成 IRP 并将其发送到设备堆栈。 堆栈可能会立即完成 IRP,也可能返回挂起状态,指示请求正在进行。 发生这种情况时,IRP 仍与线程关联,因此,如果线程终止或调用 CancelIo 等函数,IRP 将被取消。 在此期间,线程可以继续执行其他任务,同时设备堆栈继续处理 IRP。

系统可通过多种方式指示 IRP 已完成:

  • 使用操作的结果更新重叠结构,以便线程可以轮询以确定操作是否已完成。
  • 向重叠结构中的事件发出信号,以便线程可以在事件上同步并在操作完成时被唤醒。
  • 将 IRP 排队到线程的挂起的 APC,以便线程在进入可警报等待状态时执行 APC 例程,并从等待操作返回,状态指示它执行了一个或多个 APC 例程。
  • 将 IRP 排队到 I/O 完成端口,在该端口上等待的下一个线程将执行该端口。

在 I/O 完成端口上等待的线程不会处于可警报状态。 因此,如果这些线程向线程发出设置为作为 APC 完成的 IRP,则这些 IPC 完成不会及时发生;仅当线程从 I/O 完成端口获取请求,然后碰巧进入可警报等待时,才会发生这些请求。

将可等待计时器与异步过程调用配合使用