Win32编程之异步完成IO(十)

一、文件的异步写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <Windows.h>
#include <stdio.h>
 
int main() {
    HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("无法打开文件。错误码:%d\n", GetLastError());
        return 0;
    }
 
    OVERLAPPED ol1 = { 0 };
    char buffer[] = "Hello World";
    DWORD writeCount = 0;
    BOOL ret = WriteFile(hFile, buffer, strlen(buffer), &writeCount, &ol1);
    if (!ret) {
        CloseHandle(hFile);
        printf("文件写入失败!\n");
        return 0;
    }
 
    CloseHandle(hFile);
 
    return 1;
} 

二、文件的异步读取  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <Windows.h>
#include <stdio.h>
 
int main() {
    HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("无法打开文件。错误码:%d\n", GetLastError());
        return 0;
    }<br>
    OVERLAPPED ol2 = { 0 };
    char readBuffer[255] = { 0 };
    DWORD readCount = 0;
    ret = ReadFile(hFile, readBuffer, 255, &readCount, &ol2);
    if (!ret) {
        CloseHandle(hFile);
        printf("文件读取失败!\n");
        return 0;
    }
    printf("readBuffer:%s\n", readBuffer);
 
    CloseHandle(hFile);
 
    return 1;
}

 三、异步读写操作的判断方法

(1).异步文件写入操作的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
OVERLAPPED ol1 = { 0 };
char buffer[] = "Hello World";
DWORD writeCount = 0;
BOOL ret = WriteFile(hFile, buffer, strlen(buffer), &writeCount, &ol1);
if (!ret) {
    DWORD err = GetLastError();
    if (ERROR_IO_PENDING == err) {
        printf("正在进行异步写入操作!\n");
    }
    else {
        printf("文件写入失败!\n");
        CloseHandle(hFile);
        return 0;
    }
}

(2).异步文件读取操作的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
OVERLAPPED ol2 = { 0 };
char readBuffer[255] = { 0 };
DWORD readCount = 0;
ret = ReadFile(hFile, readBuffer, 255, &readCount, &ol2);
if (!ret) {
    DWORD err = GetLastError();
    if (ERROR_IO_PENDING == err) {
        printf("正在进行异步读取操作!\n");
    }
    else {
        printf("文件读取失败!\n");
        CloseHandle(hFile);
        return 0;
    }
}
printf("readBuffer:%s\n", readBuffer);

三、异步IO完成通知的方法 

1.触发设备内核对象

触发设备内核对象:允许一个线程发出IO请求,另一个线程对结果进行处理,当向一个设备同时发出多个IO请求的时候,此方法无效

(1).等待文件写入完毕

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
OVERLAPPED ol1 = { 0 };
char buffer[] = "Hello World";
DWORD writeCount = 0;
BOOL ret = WriteFile(hFile, buffer, strlen(buffer), &writeCount, &ol1);
if (!ret) {
    DWORD err = GetLastError();
    if (ERROR_IO_PENDING == err) {
        printf("正在进行异步写入操作!\n");
        WaitForSingleObject(hFile, INFINITE);
        printf("异步写入完毕!\n");
 
    }
    else {
        printf("文件写入失败!\n");
        CloseHandle(hFile);
        return 0;
        }
}

(2).等待文件读取完毕 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
OVERLAPPED ol2 = { 0 };
char readBuffer[255] = { 0 };
DWORD readCount = 0;
ret = ReadFile(hFile, readBuffer, 255, &readCount, &ol2);
if (!ret) {
    DWORD err = GetLastError();
    if (ERROR_IO_PENDING == err) {
        printf("正在进行异步读取操作!\n");
        WaitForSingleObject(hFile, INFINITE);
        printf("异步读取完毕\n");
    }
    else {
        printf("文件读取失败!\n");
        CloseHandle(hFile);
        return 0;
    }
}
printf("readBuffer:%s\n", readBuffer);

2.触发事件内核对象

这种方法允许我们向一个设备同时发出多个IO请求,它允许一个线程发出IO请求,另一个线程对结果进行处理

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <Windows.h>
#include <stdio.h>
 
int main() {
    HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("无法打开文件。错误码:%d\n", GetLastError());
        return 0;
    }
 
    OVERLAPPED ol1 = { 0 };
    ol1.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    OVERLAPPED ol2 = { 0 };
    ol2.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    char buffer[] = "Hello World";
    char readBuffer[256] = { 0 };
    DWORD writeCount = 0;
    DWORD readCount = 0;
    BOOL ret = WriteFile(hFile, buffer, strlen(buffer), &writeCount, &ol1);
    ret = ReadFile(hFile, readBuffer, strlen(readBuffer), &readCount, &ol2);
    if (!ret) {
        DWORD err = GetLastError();
        if (ERROR_IO_PENDING == err) {
            HANDLE handle[2];
            handle[0] = ol1.hEvent;
            handle[1] = ol2.hEvent;
            DWORD objnum = WaitForMultipleObjects(2, handle, TRUE, INFINITE);
            switch (objnum) {
            case WAIT_OBJECT_0: {
                printf("文件写入操作完毕\n");              
            }
            case WAIT_OBJECT_0 + 1: {
                printf("文件读取操作完毕:%s\n", readBuffer);               
            }
            default:
                break;
            }      
            CloseHandle(hFile);
            return 0;
        }
        else {
            printf("File failed\n");
        }
    }
 
    CloseHandle(ol1.hEvent);
    CloseHandle(ol2.hEvent);
    CloseHandle(hFile);
 
    return 1;
}

3.使用可提醒IO 

这种方法允许我们向一个设备发出多个IO请求,发出IO请求的线程必须对结果进行处理

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <Windows.h>
#include <stdio.h>
 
char readBuffer[256] = { 0 };
char writeBuffer[256] = "1234567890";
 
VOID WriteFunc(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) {
    printf("文件写入结束\n");
}
 
VOID ReadFunc(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) {
    printf("%s\n", readBuffer);
}
 
int main() {
    HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("无法打开文件。错误码:%d\n", GetLastError());
        return 0;
    }
    OVERLAPPED ol2 = { 0 };
     
    BOOL ret = WriteFileEx(hFile, writeBuffer, strlen(writeBuffer), &ol2, (LPOVERLAPPED_COMPLETION_ROUTINE)WriteFunc);
    SleepEx(1000, true);
    if (!ret) {
        DWORD err = GetLastError();
        if (ERROR_IO_PENDING == err) {
            printf("文件写入中!!!\n");
        }
        else {
            printf("文件写入失败!!!\n");
            CloseHandle(hFile);
            return 0;
        }
    }
 
    ret = ReadFileEx(hFile, readBuffer, sizeof(readBuffer), &ol2, (LPOVERLAPPED_COMPLETION_ROUTINE)ReadFunc);
    //SleepEx(1000, true);
    WaitForSingleObjectEx(hFile, INFINITE, true);
    if (!ret) {
        DWORD err = GetLastError();
        if (ERROR_IO_PENDING == err) {
            printf("文件读取中!!!\n");
        } else {
            printf("文件读取失败!!!\n");
            CloseHandle(hFile);
            return 0;
        }
    }
     
    CloseHandle(hFile);
 
    return 1;
} 

4.使用IO完成端口

这种方法允许我们向一个设备同时发出多个IO请求。它允许一个线程发出IO请求,另一个线程对结果进行处理,推荐使用,伸缩性和灵活性都很好,IO完成端口的初衷就是与线程池配合使用
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <Windows.h>
#include <stdio.h>
 
int main() {
    char userName[256] = { 0 };
    HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (INVALID_HANDLE_VALUE == hFile) {
        printf("文件创建失败,错误码:%d\n", GetLastError());
 
        return 1;
    }
    HANDLE hCicp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
    if (NULL == hCicp) {
        printf("创建CreateIoCompletionPort失败,错误码:%d\n", GetLastError());
        CloseHandle(hFile);
 
        return 1;
    }
    ULONG_PTR CK_READ = 0;
    CreateIoCompletionPort(hFile, hCicp, CK_READ, 0);
 
    OVERLAPPED ol = { 0 };
    ReadFile(hFile, userName, 256, NULL, &ol);
     
    DWORD transferedByte = 0;
    void* lpContext = NULL;
    OVERLAPPED* pOl = NULL;
    while (GetQueuedCompletionStatus(hCicp, &transferedByte, (LPDWORD)&lpContext, &pOl, INFINITE)) {
        printf("%s\n", userName);
    }
 
    CloseHandle(hFile);
    CloseHandle(hCicp);
 
    return 0;
}

四、串行模型与并发模型

CPU是通过时间片轮转的方式执行线程,比如说,A线程执行一段时间,然后切换到B线程,B线程执行一段时间,再切换到C线程,C线程执行一段时间,然后再切换到A线程;当CPU执行的线程过多时,可以发现,CPU处理排队线程的请求远远小于线程之间来回切换的时间,这个时候CPU貌似十分繁忙(忙着切换线程,没有去处理线程请求) 

1.IO完成端口

(1).CreateIoCompletionPort函数

CreateIoCompletionPort 函数是 Windows API 中的一个函数,用于创建输入/输出完成端口(I/O Completion Port),这是一种高效的异步 I/O 操作管理机制,常用于多线程的异步 I/O 编程。

函数原型:

1
HANDLE CreateIoCompletionPort(<br>    HANDLE FileHandle,<br>    HANDLE ExistingCompletionPort,<br>    ULONG_PTR CompletionKey,<br>    DWORD NumberOfConcurrentThreads<br>);

参数解释:

  • ExistingCompletionPort:可选参数,如果要将文件句柄关联到现有的 I/O 完成端口,则传递现有的 I/O 完成端口句柄,否则传递 NULL
  • FileHandle:要与 I/O 完成端口关联的文件句柄。
  • CompletionKey:关联的完成键,是一个用户定义的值,通常用于标识关联的文件句柄。
  • NumberOfConcurrentThreads:指定可以同时执行 I/O 操作的线程数目。

返回值:

如果函数调用成功,将返回新创建的 I/O 完成端口的句柄(HANDLE)。如果函数失败,则返回 NULL,您可以通过调用 GetLastError 获取错误信息。

(2).GetQueuedCompletionStatuse函数

GetQueuedCompletionStatus 函数是 Windows API 中用于从 I/O 完成端口 (I/O Completion Port) 中获取已完成的 I/O 操作的函数。它通常用于与异步 I/O 操作相关的多线程编程,以便检查和处理已完成的 I/O 操作结果。

函数原型:

1
2
3
4
5
6
7
BOOL GetQueuedCompletionStatus(
  HANDLE       CompletionPort,
  LPDWORD      lpNumberOfBytesTransferred,
  PULONG_PTR   lpCompletionKey,
  LPOVERLAPPED *lpOverlapped,
  DWORD        dwMilliseconds
);

 参数解释:

  • CompletionPort:要从中获取已完成 I/O 操作的 I/O 完成端口句柄。
  • lpNumberOfBytesTransferred:用于接收已传输字节数的指针。如果不关心传输的字节数,可以传递 NULL
  • lpCompletionKey:用于接收已完成 I/O 操作关联的完成键的指针。完成键通常用于标识 I/O 操作的类型或源。
  • lpOverlapped:用于接收指向 OVERLAPPED 结构的指针,该结构与已完成的 I/O 操作相关联。如果不关心此参数,可以传递 NULL
  • dwMilliseconds:等待时间,以毫秒为单位。如果没有已完成的 I/O 操作,函数将阻塞等待指定的时间。如果传递零,函数将立即返回,如果传递 INFINITE,函数将无限期地等待。

返回值:

如果函数调用成功并且获取了已完成的 I/O 操作,它将返回 TRUE。如果函数调用失败或等待超时,它将返回 FALSE。您可以通过调用 GetLastError 获取错误信息。

(3).PostQueuedCompletionStatus函数

PostQueuedCompletionStatus 函数是 Windows API 中用于将已完成的 I/O 操作结果(或其他自定义完成状态)添加到 I/O 完成端口 (I/O Completion Port) 队列的函数。它通常用于多线程编程中,用于将异步操作的结果通知给使用 I/O 完成端口的线程。 

函数原型:

1
2
3
4
5
6
BOOL PostQueuedCompletionStatus(
  HANDLE       CompletionPort,
  DWORD        dwNumberOfBytesTransferred,
  ULONG_PTR    dwCompletionKey,
  LPOVERLAPPED lpOverlapped
);

参数解释:

  • CompletionPort:要将完成状态添加到的 I/O 完成端口句柄。
  • dwNumberOfBytesTransferred:指定已传输的字节数。通常用于通知 I/O 完成端口关于 I/O 操作的结果。
  • dwCompletionKey:指定完成状态的关键字,通常用于标识 I/O 操作的类型或源。
  • lpOverlapped:指向 OVERLAPPED 结构的指针,通常用于关联已完成的 I/O 操作。可以为 NULL

返回值:

如果函数调用成功,将返回 TRUE。如果函数调用失败,将返回 FALSE。您可以通过调用 GetLastError 获取错误信息。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <Windows.h>
#include <stdio.h>
#include <process.h>
 
HANDLE hCicp = NULL;
unsigned int ThreadFunc(void* arg) {
    getchar();
    ULONG_PTR key = 10;
    OVERLAPPED ol = { 0 };
    PostQueuedCompletionStatus(hCicp, 4, 10, &ol);
 
    return 0;
}
 
int main() {
    char userName[256] = { 0 };
    HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (INVALID_HANDLE_VALUE == hFile) {
        printf("文件创建失败,错误码:%d\n", GetLastError());
 
        return 1;
    }
    hCicp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
    if (NULL == hCicp) {
        printf("创建CreateIoCompletionPort失败,错误码:%d\n", GetLastError());
        CloseHandle(hFile);
 
        return 1;
    }
    ULONG_PTR CK_READ = 0;
    CreateIoCompletionPort(hFile, hCicp, CK_READ, 0);
    unsigned int uiThreadID = 0;
    HANDLE hThread =  (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)ThreadFunc, NULL, 0, &uiThreadID);
 
    OVERLAPPED ol = { 0 };
    ReadFile(hFile, userName, 256, NULL, &ol);
 
    DWORD transferedByte = 0;
    void* lpContext = NULL;
    OVERLAPPED* pOl = NULL;
    while (GetQueuedCompletionStatus(hCicp, &transferedByte, (LPDWORD)&lpContext, &pOl, INFINITE)) {
        if (lpContext != NULL && 10 == (unsigned long)lpContext) {
            printf("IO端口完成退出!\n");
            break;
        }
         
        printf("%s\n", userName);
    }
    CloseHandle(hThread);
    CloseHandle(hFile);
    CloseHandle(hCicp);
 
    return 0;
}

 

posted @   TechNomad  阅读(98)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示