使用协程从SQLite incrblob传输大BLOB至浏览器是否真的非阻塞?
问题描述
我想在不把BLOB完整加载到内存、同时不阻塞Tcl脚本的前提下,从SQLite数据库向浏览器传输BLOB。原本带-command参数的chan copy可以实现非阻塞传输(已在文件场景验证),但这个特性无法和SQLite的incrblob配合使用。
试过多种方案未果后,参考协程相关示例编写了基于协程的实现:将250MB视频BLOB通过浏览器的sock通道分块传输,每次在通道可写事件触发时发送4096字节数据,视频可正常播放。
现提出三个技术疑问:
- 该方案用于桌面应用的小型本地服务器是否可行?
- 该实现是否真的是非阻塞的?已将两个通道设为
-blocking 0,协程会交还控制权,但我对其非阻塞原理理解不深,且难以测试。 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
相关产品推荐
相关产品推荐

