You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用协程从SQLite incrblob传输大BLOB至浏览器是否真的非阻塞?

问题描述

我想在不把BLOB完整加载到内存、同时不阻塞Tcl脚本的前提下,从SQLite数据库向浏览器传输BLOB。原本带-command参数的chan copy可以实现非阻塞传输(已在文件场景验证),但这个特性无法和SQLite的incrblob配合使用。

试过多种方案未果后,参考协程相关示例编写了基于协程的实现:将250MB视频BLOB通过浏览器的sock通道分块传输,每次在通道可写事件触发时发送4096字节数据,视频可正常播放。

现提出三个技术疑问:

  1. 该方案用于桌面应用的小型本地服务器是否可行?
  2. 该实现是否真的是非阻塞的?已将两个通道设为-blocking 0,协程会交还控制权,但我对其非阻塞原理理解不深,且难以测试。
  3. incrblob无法配合chan copy -command的原因是什么?是否因为通道归属不同解释器导致事件异常?

附实现代码

proc ::REQ::SendBLOB {sock filename type id} {
  dbws eval {select rowid, length(col_blob) as bytes from db.table where id=:id} {
    # Get the BLOB as a (read-only) channel
    set fp [dbws incrblob -readonly db table col_blob $rowid]
    chan configure $fp -translation binary -blocking 0
    chan configure $sock -translation binary -blocking 0
    # Would use chan copy with -command but it will not work with incrblob.
    # chan copy $fp $sock -size $bytes -command [list ::REQ::ChanCopyCleanup $fp]
  }
  coroutine ::coro_incrblob${sock} proxyfcopy $fp $sock $bytes
  chan event $sock writable ::coro_incrblob${sock}
}

proc proxyfcopy {fp sock bytes} {
  while { ![chan eof $fp] } {
    yield
    # Think this 4096 should be at least 5,120,000; for it appears
    # that Firefox requests 60 MB at first load and 5 MB thereafter.
    if { $bytes > 4096 } {
      set size 4096
    } elseif { $bytes > 0 } {
      set size $bytes
    } else {
      chan event $sock writable {}
      close $fp
      return
    }
    set data [read $fp $size]
    chan puts stdout $bytes
    chan puts -nonewline $sock $data
    set bytes [expr {$bytes - [string length $data]}]
  }
}
解答

1. 桌面小型本地服务器的可行性

完全可行。桌面本地服务器并发量低,该方案能很好适配需求:

  • 分块传输避免了大BLOB一次性占用内存,不会引发内存过载问题;
  • 协程配合通道可写事件的模式,能保证服务器同时处理其他任务(比如桌面界面交互、其他客户端请求);
  • 视频正常播放已验证传输正确性,若根据浏览器请求调整分块大小(比如适配Firefox的60MB初始请求、5MB后续请求),体验会进一步优化。

2. 非阻塞性的确认与测试

该实现确实是非阻塞的,核心逻辑:

  • 两个通道设为-blocking 0后,read和puts操作不会因等待数据或缓冲区而阻塞,会立即返回;
  • 协程中的yield会将控制权交还给Tcl事件循环,只有当sock通道可写时,事件循环才会唤醒协程执行一次数据发送;
  • 每次发送完成后协程再次yield,不会长时间占用事件循环,其他事件(如定时器、其他通道IO)可正常处理。

测试方法:在传输过程中添加定时器(比如after 1000 {puts "Timer triggered"}),若定时器能正常输出,说明事件循环未被阻塞;或同时发起多个BLOB传输请求,观察是否能并行处理。

3. incrblob无法配合chan copy -command的原因

核心原因是SQLiteincrblob通道与普通文件通道的异步IO支持逻辑不同:

  • chan copy -command依赖通道的IO事件通知机制,但incrblob是基于数据库连接的逻辑通道,未注册到Tcl事件循环中,无法触发异步读写事件;
  • 此外,incrblob通道的生命周期与数据库事务绑定,若异步chan copy过程中数据库连接状态变化(如事务结束),会直接导致通道失效,这也是Tcl官方未为其适配异步chan copy的重要原因;
  • 你猜测的“通道归属不同解释器”并不准确,本质是incrblob通道未实现Tcl异步IO所需的事件回调接口。

内容的提问来源于stack exchange,提问作者Gary

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 11:45:34