异步IO子系统(CategoryAsyncIO)
SDL 提供了异步执行 I/O 操作的方式,允许应用程序在读写文件时无需等待数据实际传输完成——发起 I/O 请求的函数在请求处理期间永远不会阻塞。
数据会在后台传输,应用程序可在合适的时机检查操作结果。
这种方式比同步读写文件更复杂,但能提升效率,避免因硬盘读写延迟导致帧率下降等问题。
异步IO通用使用流程
- 创建一个或多个
SDL_AsyncIOQueue(异步IO队列)对象; - 通过
SDL_AsyncIOFromFile打开文件,创建异步IO句柄; - 调用
SDL_ReadAsyncIO或SDL_WriteAsyncIO启动文件的IO任务,并将任务放入指定队列; - 后续通过
SDL_GetAsyncIOResult检查队列,非阻塞地获取已完成的任务结果(任务可能以任意顺序成功/失败完成); - 所有任务完成后,调用
SDL_CloseAsyncIO关闭文件(这也会生成一个任务,因为可能需要将缓存刷入磁盘)。
上述流程可在单线程中无阻塞运行,也可在后台线程中等待队列:
- 在一个或多个线程中调用
SDL_WaitAsyncIOResult,高效阻塞直到有新任务完成; - 程序关闭时,调用
SDL_SignalAsyncIOQueue唤醒所有休眠线程(即使无新任务完成)。
此外,为匹配同步的 SDL_LoadFile,SDL 提供了便捷函数 SDL_LoadFileAsync——自动分配缓冲区、读取整个文件数据并添加空终止符,你只需后续检查结果即可。
底层实现
SDL 会在支持的平台上使用高效的新API(如 Linux 的 io_uring、Windows 11 的 IoRing);若这些技术不可用,SDL 会将任务卸载到线程池,在不阻塞应用程序的前提下执行同步加载。
最佳实践
- 简单非阻塞IO:仅需在数据就绪时获取、避免磁盘等待导致帧率丢失的应用,可按需调用
SDL_ReadAsyncIO/SDL_LoadFileAsync,每帧调用SDL_GetAsyncIOResult检查完成的任务并处理数据; - 队列管理:程序不同模块需独立IO时,可各自创建队列(避免误消费对方的任务结果);队列会占用少量资源,但无需为每个任务创建队列——多个任务放入单个队列更高效(任务按完成顺序返回,与提交顺序无关);
- 线程与队列匹配:若追求极致效率,建议「一线程一队列」,多线程并行处理,且任务的发起和结果消费由同一线程完成(现代平台可最大化数据传输效率,避免线程间队列访问竞争);
- 数据刷盘:即使关闭任务完成,写入的数据也不保证已写入物理介质——需将
SDL_CloseAsyncIO的flush参数设为 true 强制刷盘(否则断电可能导致数据丢失);但刷盘耗时更长,可根据应用需求选择是否执行。
函数
- SDL_AsyncIOFromFile:从文件创建异步IO句柄(指定文件路径和打开模式,返回 SDL_AsyncIO 句柄)
- SDL_CloseAsyncIO:关闭异步IO句柄(生成关闭任务,可指定是否刷盘,任务完成后释放文件资源)
- SDL_CreateAsyncIOQueue:创建异步IO队列(用于管理异步IO任务,返回队列句柄)
- SDL_DestroyAsyncIOQueue:销毁异步IO队列(释放队列资源,需确保队列中无未处理的任务)
- SDL_GetAsyncIOResult:非阻塞获取队列中已完成的任务结果(返回首个完成的任务,无完成任务则返回空)
- SDL_GetAsyncIOSize:获取异步IO句柄对应文件/流的总大小(字节),仅支持可定位的数据源
- SDL_LoadFileAsync:异步加载整个文件到内存(自动分配缓冲区,添加到队列,完成后数据带空终止符)
- SDL_ReadAsyncIO:发起异步读任务(指定读取缓冲区、大小、偏移,任务加入队列后立即返回)
- SDL_SignalAsyncIOQueue:唤醒等待该队列的所有线程(即使无新任务完成,用于程序退出时清理)
- SDL_WaitAsyncIOResult:阻塞等待队列中的任务完成(直到有任务完成或被信号唤醒,返回完成的任务)
- SDL_WriteAsyncIO:发起异步写任务(指定写入缓冲区、大小、偏移,任务加入队列后立即返回)
数据类型
- SDL_AsyncIO:异步IO句柄类型(标识一个打开的异步文件/流,所有异步IO操作基于此句柄)
- SDL_AsyncIOQueue:异步IO队列类型(管理异步IO任务的队列,任务按完成顺序存储结果)
结构体
- SDL_AsyncIOOutcome:异步IO任务结果结构体(包含任务类型、句柄、操作字节数、错误状态等)
枚举
- SDL_AsyncIOResult:异步IO结果枚举(成功、失败、队列空、被信号唤醒等)
- SDL_AsyncIOTaskType:异步IO任务类型枚举(读、写、关闭、加载文件等)
宏
- (无)
FreeBASIC 示例代码
' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库)
#Include "SDL.bi"
' 补充枚举/结构体/函数声明(FreeBASIC 绑定可能缺失)
Enum SDL_AsyncIOResult
SDL_ASYNC_IO_SUCCESS = 0 ' 任务成功
SDL_ASYNC_IO_FAILED = 1 ' 任务失败
SDL_ASYNC_IO_QUEUE_EMPTY = 2 ' 队列无完成任务
SDL_ASYNC_IO_SIGNALED = 3 ' 被信号唤醒(无任务)
End Enum
Enum SDL_AsyncIOTaskType
SDL_ASYNC_IO_TASK_READ = 0 ' 读任务
SDL_ASYNC_IO_TASK_WRITE = 1 ' 写任务
SDL_ASYNC_IO_TASK_CLOSE = 2 ' 关闭任务
SDL_ASYNC_IO_TASK_LOAD_FILE = 3 ' 加载文件任务
End Enum
Type SDL_AsyncIO As Any Ptr ' 异步IO句柄
Type SDL_AsyncIOQueue As Any Ptr ' 异步IO队列
Type SDL_AsyncIOOutcome
task_type As SDL_AsyncIOTaskType ' 任务类型
aio As SDL_AsyncIO ' 关联的异步IO句柄
bytes_processed As ULLong ' 处理的字节数
result As SDL_AsyncIOResult ' 任务结果
userdata As Any Ptr ' 用户自定义数据
End Type
' 核心函数声明
Declare Function SDL_CreateAsyncIOQueue CDecl () As SDL_AsyncIOQueue
Declare Sub SDL_DestroyAsyncIOQueue CDecl (ByVal queue As SDL_AsyncIOQueue)
Declare Function SDL_AsyncIOFromFile CDecl (ByVal path As ZString Ptr, ByVal mode As ZString Ptr) As SDL_AsyncIO
Declare Function SDL_ReadAsyncIO CDecl (ByVal queue As SDL_AsyncIOQueue, ByVal aio As SDL_AsyncIO, _
ByVal buffer As Any Ptr, ByVal size As ULLong, ByVal offset As LongInt, ByVal userdata As Any Ptr) As Integer
Declare Function SDL_WriteAsyncIO CDecl (ByVal queue As SDL_AsyncIOQueue, ByVal aio As SDL_AsyncIO, _
ByVal buffer As Any Ptr, ByVal size As ULLong, ByVal offset As LongInt, ByVal userdata As Any Ptr) As Integer
Declare Function SDL_CloseAsyncIO CDecl (ByVal queue As SDL_AsyncIOQueue, ByVal aio As SDL_AsyncIO, _
ByVal flush As Integer, ByVal userdata As Any Ptr) As Integer
Declare Function SDL_GetAsyncIOResult CDecl (ByVal queue As SDL_AsyncIOQueue, ByVal outcome As SDL_AsyncIOOutcome Ptr) As Integer
Declare Function SDL_WaitAsyncIOResult CDecl (ByVal queue As SDL_AsyncIOQueue, ByVal outcome As SDL_AsyncIOOutcome Ptr) As Integer
Declare Sub SDL_SignalAsyncIOQueue CDecl (ByVal queue As SDL_AsyncIOQueue)
Declare Function SDL_LoadFileAsync CDecl (ByVal queue As SDL_AsyncIOQueue, ByVal path As ZString Ptr, _
ByVal userdata As Any Ptr) As Integer
Declare Sub SDL_free CDecl (ByVal ptr As Any Ptr)
' 打印异步IO任务结果
Sub PrintAsyncResult(ByVal outcome As SDL_AsyncIOOutcome Ptr)
Dim As ZString * 20 taskTypeStr
Select Case outcome->task_type
Case SDL_ASYNC_IO_TASK_READ: taskTypeStr = "读任务"
Case SDL_ASYNC_IO_TASK_WRITE: taskTypeStr = "写任务"
Case SDL_ASYNC_IO_TASK_CLOSE: taskTypeStr = "关闭任务"
Case SDL_ASYNC_IO_TASK_LOAD_FILE: taskTypeStr = "加载文件任务"
Case Else: taskTypeStr = "未知任务"
End Select
Dim As ZString * 20 resultStr
Select Case outcome->result
Case SDL_ASYNC_IO_SUCCESS: resultStr = "成功"
Case SDL_ASYNC_IO_FAILED: resultStr = "失败"
Case SDL_ASYNC_IO_QUEUE_EMPTY: resultStr = "队列空"
Case SDL_ASYNC_IO_SIGNALED: resultStr = "被信号唤醒"
Case Else: resultStr = "未知结果"
End Select
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "任务结果:")
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, " 任务类型:%s", taskTypeStr)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, " 处理字节数:%llu", outcome->bytes_processed)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, " 执行结果:%s", resultStr)
End Sub
' 示例1:异步读写文件
Sub AsyncFileRWExample()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "【示例1:异步读写文件】")
' 1. 创建异步IO队列
Dim As SDL_AsyncIOQueue Ptr queue = SDL_CreateAsyncIOQueue()
If (queue = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建异步IO队列失败:%s", SDL_GetError())
Exit Sub
End If
' 2. 打开异步文件句柄(可写模式)
Dim As SDL_AsyncIO Ptr aio = SDL_AsyncIOFromFile(StrPtr("async_test.txt"), StrPtr("wb"))
If (aio = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "打开异步文件失败:%s", SDL_GetError())
SDL_DestroyAsyncIOQueue(queue)
Exit Sub
End If
' 3. 准备写入数据并发起异步写任务
Dim As ZString * 100 writeData = "SDL 异步IO测试数据 - 写入内容"
Dim As ULLong writeSize = Len(writeData)
If (SDL_WriteAsyncIO(queue, aio, @writeData, writeSize, 0, NULL) = 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "发起异步写任务失败:%s", SDL_GetError())
SDL_DestroyAsyncIOQueue(queue)
Exit Sub
End If
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "发起异步写任务(大小:%llu 字节)", writeSize)
' 4. 等待写任务完成(阻塞方式)
Dim As SDL_AsyncIOOutcome writeOutcome
If (SDL_WaitAsyncIOResult(queue, @writeOutcome) = 1) Then
PrintAsyncResult(@writeOutcome)
End If
' 5. 重新打开文件(只读模式),发起异步读任务
SDL_CloseAsyncIO(queue, aio, 1, NULL) ' 先关闭写句柄(刷盘)
aio = SDL_AsyncIOFromFile(StrPtr("async_test.txt"), StrPtr("rb"))
If (aio = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "重新打开异步文件失败:%s", SDL_GetError())
SDL_DestroyAsyncIOQueue(queue)
Exit Sub
End If
' 分配读缓冲区
Dim As Any Ptr readBuffer = SDL_malloc(writeSize + 1)
If (readBuffer = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "内存分配失败")
SDL_DestroyAsyncIOQueue(queue)
Exit Sub
End If
' 发起读任务
If (SDL_ReadAsyncIO(queue, aio, readBuffer, writeSize, 0, NULL) = 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "发起异步读任务失败:%s", SDL_GetError())
SDL_free(readBuffer)
SDL_DestroyAsyncIOQueue(queue)
Exit Sub
End If
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "发起异步读任务(大小:%llu 字节)", writeSize)
' 6. 非阻塞检查读任务结果(模拟每帧检查)
Dim As Integer checkCount = 0
Dim As SDL_AsyncIOOutcome readOutcome
Do
checkCount += 1
If (SDL_GetAsyncIOResult(queue, @readOutcome) = 1) Then
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "第%d次检查:读任务完成", checkCount)
PrintAsyncResult(@readOutcome)
Exit Do
End If
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "第%d次检查:读任务未完成,等待10ms...", checkCount)
SDL_Delay(10) ' 模拟帧间隔
Loop Until checkCount >= 100
' 7. 打印读取内容
If (readOutcome.result = SDL_ASYNC_IO_SUCCESS) Then
Dim As ZString Ptr readStr = Cast(ZString Ptr, readBuffer)
readStr[readOutcome.bytes_processed] = 0
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "读取内容:%s", *readStr)
End If
' 8. 发起关闭任务(刷盘)
Dim As SDL_AsyncIOOutcome closeOutcome
SDL_CloseAsyncIO(queue, aio, 1, NULL)
SDL_WaitAsyncIOResult(queue, @closeOutcome)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "关闭任务结果:%s", IIf(closeOutcome.result=SDL_ASYNC_IO_SUCCESS, "成功", "失败"))
' 9. 释放资源
SDL_free(readBuffer)
SDL_SignalAsyncIOQueue(queue) ' 唤醒可能等待的线程
SDL_DestroyAsyncIOQueue(queue)
End Sub
' 示例2:异步加载整个文件
Sub AsyncLoadFileExample()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, vbCrLf & "【示例2:异步加载整个文件】")
' 1. 创建队列
Dim As SDL_AsyncIOQueue Ptr queue = SDL_CreateAsyncIOQueue()
If (queue = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建异步IO队列失败:%s", SDL_GetError())
Exit Sub
End If
' 2. 发起异步加载文件任务
Dim As Integer userdata = 123 ' 自定义用户数据
If (SDL_LoadFileAsync(queue, StrPtr("async_test.txt"), @userdata) = 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "发起异步加载文件任务失败:%s", SDL_GetError())
SDL_DestroyAsyncIOQueue(queue)
Exit Sub
End If
' 3. 等待任务完成
Dim As SDL_AsyncIOOutcome loadOutcome
If (SDL_WaitAsyncIOResult(queue, @loadOutcome) = 1) Then
PrintAsyncResult(@loadOutcome)
' 4. 处理加载结果(数据在 userdata 指向的缓冲区)
If (loadOutcome.result = SDL_ASYNC_IO_SUCCESS AndAlso loadOutcome.userdata <> NULL) Then
Dim As ZString Ptr fileData = Cast(ZString Ptr, loadOutcome.userdata)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "异步加载文件内容:%s", *fileData)
SDL_free(fileData) ' 释放 SDL_LoadFileAsync 分配的内存
End If
End If
' 5. 清理资源
SDL_DestroyAsyncIOQueue(queue)
End Sub
' 主程序
Sub Main()
' 初始化 SDL
If (SDL_Init(0) < 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError())
Exit Sub
End If
' 运行示例
AsyncFileRWExample()
AsyncLoadFileExample()
' 清理 SDL
SDL_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, vbCrLf & "程序正常退出")
End Sub
' 运行主程序
Main()
核心知识点补充
-
异步IO核心特性:
- 非阻塞:所有任务发起函数立即返回,IO操作在后台执行;
- 结果获取:支持两种方式——
SDL_GetAsyncIOResult(非阻塞,适合帧循环)、SDL_WaitAsyncIOResult(阻塞,适合后台线程); - 任务顺序:任务按完成顺序返回结果,与提交顺序无关;
- 跨平台优化:自动适配平台原生高效异步IO API,无原生支持时使用线程池兼容。
-
队列管理规则:
- 每个队列独立管理任务,不同队列的任务结果不会互相干扰;
- 程序退出前必须调用
SDL_SignalAsyncIOQueue唤醒等待队列的线程,避免线程卡死; - 销毁队列前需确保所有任务完成,或通过
SDL_SignalAsyncIOQueue强制唤醒。
-
刷盘与数据安全:
SDL_CloseAsyncIO的flush参数设为1时,会强制将缓存写入物理介质(避免断电丢失);- 刷盘操作会增加耗时,非关键数据可设为
0提升效率; - 即使任务返回「成功」,未刷盘的数据仍可能停留在系统缓存中。
-
SDL_LoadFileAsync 特性:
- 自动分配与文件大小匹配的缓冲区,无需手动计算;
- 数据末尾自动添加空终止符,可直接作为字符串使用;
- 完成后需通过
SDL_free释放缓冲区,而非平台原生free。
总结
-
核心优势:
- 非阻塞IO操作,避免主线程/帧循环因磁盘IO阻塞导致卡顿;
- 自动适配平台高效异步IO API,兼顾性能与兼容性;
- 提供队列管理机制,支持多任务/多线程安全处理;
- 便捷的
SDL_LoadFileAsync函数,简化异步加载整个文件的流程。
-
使用建议:
- 游戏/实时应用中,优先使用非阻塞的
SDL_GetAsyncIOResult每帧检查任务结果; - 后台数据处理线程中,使用
SDL_WaitAsyncIOResult高效等待任务; - 关键数据写入后,关闭文件时务必开启
flush确保数据持久化; - 多个独立模块使用异步IO时,为每个模块创建独立队列,避免结果混淆。
- 游戏/实时应用中,优先使用非阻塞的
-
关键接口:
- 队列管理:
SDL_CreateAsyncIOQueue()/SDL_DestroyAsyncIOQueue()/SDL_SignalAsyncIOQueue(); - 任务发起:
SDL_AsyncIOFromFile()/SDL_ReadAsyncIO()/SDL_WriteAsyncIO()/SDL_LoadFileAsync(); - 结果获取:
SDL_GetAsyncIOResult()/SDL_WaitAsyncIOResult(); - 资源清理:
SDL_CloseAsyncIO()(务必调用,且按需刷盘)。
- 队列管理:
评论一下?