Windows中潜在的异步(重叠)I / O实现

时间:2012-06-06 09:12:28

标签: windows winapi asynchronous io overlapped-io

我想在Windows中讨论潜在的异步(Overlapped)I / O实现,因为有很多方法可以实现它。 Windows中重叠的I / O提供了异步处理数据的能力,即操作的执行是非阻塞的。

编辑:这个问题的目的一方面是关于改进我自己实施的讨论,另一方面是关于替代实施的讨论。什么异步I / O实现对并行繁重的I / O最有意义,在小型的大多数单线程应用程序中最有意义。

我会引用MSDN

  

同步执行某个功能时,直到操作完成后才会返回。这意味着在等待耗时的操作完成时,可以无限期地阻止调用线程的执行。即使操作尚未完成,调用重叠操作的函数也可以立即返回。这使得在后台执行耗时的I / O操作,同时调用线程可以自由执行其他任务。例如,单个线程可以在不同的句柄上执行同时的I / O操作,甚至可以在同一个句柄上同时执行读写操作。

我认为读者熟悉重叠I / O的基本概念。

异步I / O的另一种解决方案是完成端口,但这不是本讨论的主题。有关其他I / O概念的更多信息,请访问MSDN“About File Management > Input and Output (I/O) > I/O Concepts

我想在这里展示我的(C / C ++)实现并分享讨论。

这是我的扩展OVERLAPPED结构,名为IoOperation

struct IoOperation : OVERLAPPED {
    HANDLE Handle;
    unsigned int Operation;
    char* Buffer;
    unsigned int BufferSize;
}

每次调用ReadFileWriteFile等异步操作时,都会创建此结构。 Handle字段应使用相应的设备/文件句柄进行初始化。 Operation是一个用户定义的字段,用于说明调用的操作。字段Buffer是指向先前分配的具有给定大小BufferSize的内存块的指针。当然,这个结构可以随意扩展。它可以包含操作结果,acutaully transfered size等。

我们需要的第一件事是每次重叠I / O完成时都要发出一个(自动重置)事件句柄。

HANDLE hEvent = CreateEvent(0, FALSE, FALSE, 0);

首先,我决定只对所有异步操作使用一个事件。然后我决定使用RegisterWaitForSingleObject的线程池线程注册此事件。

HANDLE hWait = 0;
....
RegisterWaitForSingleObject(
    &hWait,
    hEvent,
    WaitOrTimerCallback,
    this,
    INFINITE,
    WT_EXECUTEINPERSISTENTTHREAD | WT_EXECUTELONGFUNCTION
);

因此每次发出此事件信号时,都会调用我的回调WaitOrTimerCallback

异步操作初始化如下:

IoOperation* Io = new IoOperation(hFile, hEvent, IoOperation::Write, Data, DataSize);
if (IoQueue->Enqueue(Io)) {
    WriteFile(hFile, Io->Buffer, Io->BufferSize, 0, Io);
}

每个操作都已排队,并且在我的GetOverlappedResult回调中成功WaitOrTimerCallback调用后将被删除。而是在这里一直调用new,我们可以使用内存池来避免内存碎片并更快地进行分配。

VOID CALLBACK WaitOrTimerCallback(PVOID Parameter, BOOLEAN TimerOrWaitFired) {
    list<IoOperation*>::iterator it = IoQueue.begin();
    while (it != IoQueue.end()) {
        bool IsComplete = true;
        DWORD Transfered = 0;

        IoOperation* Io = *it;
        if (GetOverlappedResult(Io->Handle, Io, &Transfered, FALSE)) {
            if (Io->Operation == IoOperation::Read) {
                // Handle Read, virtual OnRead(), SetEvent, etc.
            } else if (Io->Operation == IoOperation::Write) {
                // Handle Read, virtual OnWrite(), SetEvent, etc.
            } else {
                // ...
            }
        } else {
            if (GetLastError() == ERROR_IO_INCOMPLETE) {
                IsComplete = false;
            } else {
                // Handle Error
            }
        }
        if (IsComplete) {
            delete Io;
            it = IoQueue.erase(it);
        } else {
            it++;
        }
    }
}

当然,为了保证多线程安全,我们在访问I / O队列时需要一个锁保护(关键部分)。

这种实现有优点但也有缺点。

优点

  • 在持久线程池线程中执行,不需要手动创建线程
  • 只需要一个活动
  • 每个操作都在I / O队列中排队(稍后可以调用CancelIoEx)

缺点

  • I / O队列需要额外的内存/ CPU时间
  • 为所有排队的I / O甚至未完成的I / O调用GetOverlappedResult

0 个答案:

没有答案