Scala继承App Trait遇死循环,定义main方法正常的原因探究
继承Scala App Trait出现无限阻塞的底层原因
问题场景
尝试通过两种方式运行Scala代码:
- 继承
App特质 - 定义独立的
main方法
核心业务代码:
val listOf5Strings = List("a", "b", "c", "d", "e") def retriveInList(list: List[String], index: Int): Future[Option[String]] = if(index > -1 && index < list.size) Future.successful(Some(list(index))) else Future.successful(None) val listOfIntegers = List(1,5,-4,20) def concatRetrievedString(stringList: List[String], integersList: List[Int]): Future[String] = integersList.foldLeft(Future.successful(""))( (acc: Future[String], index: Int) => { val valueFut = retriveInList(stringList, index) for { accVal <- acc valueOpt <- valueFut value <- Future.successful(valueOpt match { case Some(v) => accVal + v case None => accVal } ) } yield value } )
打印执行代码:
println(Await.result(retriveInList(listOf5Strings, 2), Duration.Inf)) println(Await.result(concatRetrievedString(listOf5Strings, listOfIntegers), Duration.Inf))
现象:继承App特质时程序陷入无限阻塞(表现为类似无限循环的状态),而使用独立main方法时执行完全正常。
底层原因
1. App特质的执行逻辑
App特质依赖DelayedInit特质实现延迟初始化:编译器会将类中所有非惰性的顶级代码(包括val定义、执行语句)包装到delayedInit方法中,而非在类加载阶段直接执行。App自带的main方法会触发这些delayedInit代码的执行,且执行线程为主线程。
2. 阻塞的核心诱因
当你在delayedInit代码中调用Await.result时,会直接阻塞主线程,等待Future完成。此时会触发两个关键问题:
- 虽然代码中的
Future都是Future.successful(立即完成的异步任务),但DelayedInit的代码包装逻辑会打乱执行时序,导致主线程无法正确感知Future的完成信号,陷入持续等待。 App的main方法会在delayedInit执行完成后调用sys.exit(0)终止程序,但如果主线程被Await.result阻塞,sys.exit永远无法执行,程序会一直处于阻塞状态,看起来像"无限循环"。
3. main方法正常的原因
使用独立main方法时,顶级代码(如listOf5Strings等val的初始化)会在类加载阶段完成,main方法内的代码直接在主线程执行。Await.result等待的是已完成的Future,会立即返回结果,随后程序正常退出,不存在阻塞问题。
解决方案
如果要使用App特质,可通过以下方式避免此类问题:
- 将
Await调用放在惰性代码块中,或使用lazy val延迟初始化需要异步等待的变量。 - 显式指定独立的
ExecutionContext,避免和主线程的执行逻辑冲突。 - 优先使用独立
main方法替代App特质,尤其是涉及异步操作和线程阻塞的场景。
内容的提问来源于stack exchange,提问作者Janani
相关产品推荐
相关产品推荐

