SDL3_API分类参考_异步IO(CategoryAsyncIO)

2026-3-6 / 0 评论 / 9 阅读

异步IO子系统(CategoryAsyncIO)

SDL 提供了异步执行 I/O 操作的方式,允许应用程序在读写文件时无需等待数据实际传输完成——发起 I/O 请求的函数在请求处理期间永远不会阻塞。

数据会在后台传输,应用程序可在合适的时机检查操作结果。

这种方式比同步读写文件更复杂,但能提升效率,避免因硬盘读写延迟导致帧率下降等问题。

异步IO通用使用流程

  1. 创建一个或多个 SDL_AsyncIOQueue(异步IO队列)对象;
  2. 通过 SDL_AsyncIOFromFile 打开文件,创建异步IO句柄;
  3. 调用 SDL_ReadAsyncIOSDL_WriteAsyncIO 启动文件的IO任务,并将任务放入指定队列;
  4. 后续通过 SDL_GetAsyncIOResult 检查队列,非阻塞地获取已完成的任务结果(任务可能以任意顺序成功/失败完成);
  5. 所有任务完成后,调用 SDL_CloseAsyncIO 关闭文件(这也会生成一个任务,因为可能需要将缓存刷入磁盘)。

上述流程可在单线程中无阻塞运行,也可在后台线程中等待队列:

  • 在一个或多个线程中调用 SDL_WaitAsyncIOResult,高效阻塞直到有新任务完成;
  • 程序关闭时,调用 SDL_SignalAsyncIOQueue 唤醒所有休眠线程(即使无新任务完成)。

此外,为匹配同步的 SDL_LoadFile,SDL 提供了便捷函数 SDL_LoadFileAsync——自动分配缓冲区、读取整个文件数据并添加空终止符,你只需后续检查结果即可。

底层实现

SDL 会在支持的平台上使用高效的新API(如 Linux 的 io_uring、Windows 11 的 IoRing);若这些技术不可用,SDL 会将任务卸载到线程池,在不阻塞应用程序的前提下执行同步加载。

最佳实践

  1. 简单非阻塞IO:仅需在数据就绪时获取、避免磁盘等待导致帧率丢失的应用,可按需调用 SDL_ReadAsyncIO/SDL_LoadFileAsync,每帧调用 SDL_GetAsyncIOResult 检查完成的任务并处理数据;
  2. 队列管理:程序不同模块需独立IO时,可各自创建队列(避免误消费对方的任务结果);队列会占用少量资源,但无需为每个任务创建队列——多个任务放入单个队列更高效(任务按完成顺序返回,与提交顺序无关);
  3. 线程与队列匹配:若追求极致效率,建议「一线程一队列」,多线程并行处理,且任务的发起和结果消费由同一线程完成(现代平台可最大化数据传输效率,避免线程间队列访问竞争);
  4. 数据刷盘:即使关闭任务完成,写入的数据也不保证已写入物理介质——需将 SDL_CloseAsyncIOflush 参数设为 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()

核心知识点补充

  1. 异步IO核心特性

    • 非阻塞:所有任务发起函数立即返回,IO操作在后台执行;
    • 结果获取:支持两种方式——SDL_GetAsyncIOResult(非阻塞,适合帧循环)、SDL_WaitAsyncIOResult(阻塞,适合后台线程);
    • 任务顺序:任务按完成顺序返回结果,与提交顺序无关;
    • 跨平台优化:自动适配平台原生高效异步IO API,无原生支持时使用线程池兼容。
  2. 队列管理规则

    • 每个队列独立管理任务,不同队列的任务结果不会互相干扰;
    • 程序退出前必须调用 SDL_SignalAsyncIOQueue 唤醒等待队列的线程,避免线程卡死;
    • 销毁队列前需确保所有任务完成,或通过 SDL_SignalAsyncIOQueue 强制唤醒。
  3. 刷盘与数据安全

    • SDL_CloseAsyncIOflush 参数设为 1 时,会强制将缓存写入物理介质(避免断电丢失);
    • 刷盘操作会增加耗时,非关键数据可设为 0 提升效率;
    • 即使任务返回「成功」,未刷盘的数据仍可能停留在系统缓存中。
  4. SDL_LoadFileAsync 特性

    • 自动分配与文件大小匹配的缓冲区,无需手动计算;
    • 数据末尾自动添加空终止符,可直接作为字符串使用;
    • 完成后需通过 SDL_free 释放缓冲区,而非平台原生 free

总结

  1. 核心优势

    • 非阻塞IO操作,避免主线程/帧循环因磁盘IO阻塞导致卡顿;
    • 自动适配平台高效异步IO API,兼顾性能与兼容性;
    • 提供队列管理机制,支持多任务/多线程安全处理;
    • 便捷的 SDL_LoadFileAsync 函数,简化异步加载整个文件的流程。
  2. 使用建议

    • 游戏/实时应用中,优先使用非阻塞的 SDL_GetAsyncIOResult 每帧检查任务结果;
    • 后台数据处理线程中,使用 SDL_WaitAsyncIOResult 高效等待任务;
    • 关键数据写入后,关闭文件时务必开启 flush 确保数据持久化;
    • 多个独立模块使用异步IO时,为每个模块创建独立队列,避免结果混淆。
  3. 关键接口

    • 队列管理:SDL_CreateAsyncIOQueue()/SDL_DestroyAsyncIOQueue()/SDL_SignalAsyncIOQueue()
    • 任务发起:SDL_AsyncIOFromFile()/SDL_ReadAsyncIO()/SDL_WriteAsyncIO()/SDL_LoadFileAsync()
    • 结果获取:SDL_GetAsyncIOResult()/SDL_WaitAsyncIOResult()
    • 资源清理:SDL_CloseAsyncIO()(务必调用,且按需刷盘)。

评论一下?

OωO
取消