Scala使用os.lib.watch监听目录改名触发ClosedByInterruptException如何解决
问题原因
- 回调线程限制:os.watch的事件回调默认运行在JNA底层调用的线程中,该线程对执行时长有限制,不允许执行阻塞IO类操作,运行超时会被系统主动中断,触发
ClosedByInterruptException异常 - 文件操作时序问题:文件名变更事件触发时,操作系统尚未完成重命名的收尾操作,文件句柄还处于锁定状态,此时直接读取会失败
- 依赖版本不兼容:你当前使用的
os-lib 0.7.8和os-lib-watch 0.4.2版本不匹配,底层API调用逻辑不一致也会引发异常
解决方案
1. 修正依赖
新版本os-lib已经内置目录监听能力,不需要单独引入os-lib-watch,sbt依赖修改为:
"com.lihaoyi" %% "os-lib" % "0.9.1"
如果要保留单独的os-lib-watch依赖,可将os-lib版本调整为0.4.2,保持两者版本一致即可。
2. 优化代码逻辑
- 单独启动线程池处理文件读取逻辑,不阻塞回调线程
- 读取文件前加短暂延迟,确保文件操作完成
- 增加异常捕获逻辑,兼容偶发的文件占用场景
修复后参考代码:
import java.util.concurrent.Executors import scala.concurrent.{ExecutionContext, Future} import scala.util.Try object Main extends App { // 单独创建线程池处理文件IO操作 private val ioPool = Executors.newCachedThreadPool() private implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(ioPool) def readFileContent(file: os.Path): Unit = { println("Reading Input Json..") // 加100ms延迟,等待操作系统完成文件操作 Thread.sleep(100) Try { val jsonString = os.read(file) val data = ujson.read(jsonString) println(data) }.recover { case e: Exception => println(s"读取文件失败: ${e.getMessage}, 路径: $file") } } def processFile(filePath: os.Path): Unit = { println("FileName:" + filePath) // 异步提交到IO线程池执行,不阻塞回调线程 Future(readFileContent(filePath)) } os.watch.watch( roots = Seq(os.pwd / "output"), onEvent = events => events.foreach { event => processFile(event.path) } ) // 保持主线程不退出 Thread.currentThread().join() }
补充说明
- 如果只需要监听文件名变更事件,可以在回调中增加事件类型判断,过滤不需要处理的其他事件
- 生产环境使用建议给线程池设置合理的线程数和拒绝策略,避免短时间大量事件触发导致OOM
内容的提问来源于stack exchange,提问作者uxke
相关产品推荐
相关产品推荐

