在主线程上等待回调方法

Waiting on main thread for callback methods

我是 Scala 的新手,关注 Scala Book Concurrency 部分(来自 docs.scala-lang.org)。基于他们在书中给出的示例,我编写了一个非常简单的代码块来使用 Futures 进行测试:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

object Main {
  def main(args: Array[String]): Unit = {
    val a = Future{Thread.sleep(10*100); 42}
    a.onComplete {
      case Success(x) => println(a)
      case Failure(e) => e.printStackTrace
    }
    Thread.sleep(5000)
  }
}

编译后运行,正确打印出来:

Future(Success(42))

到控制台。我无法理解为什么 Thread.sleep() 调用是在 onComplete 回调方法之后进行的。直觉上,至少对我来说,会在回调之前调用 Thread.sleep(),所以当主线程到达 onComplete 方法时,a 被分配了一个值。如果我将 Thread.sleep() 调用移动到 a.onComplete 之前,控制台不会打印任何内容。我可能想多了,但如果能帮助澄清,我将不胜感激。

Scala 中的很多东西都是函数,不一定看起来像函数。 onComplete 的参数就是其中之一。你写的是

a.onComplete {
  case Success(x) => println(a)
  case Failure(e) => e.printStackTrace
}

毕竟 Scala 的魔力是有效的(模 PartialFunction 恶作剧)

a.onComplete({ value =>
  value match {
    case Success(x) => println(a)
    case Failure(e) => e.printStackTrace
  }
})

onComplete 实际上没有做任何工作。它只是设置一个将在以后调用的函数。所以我们希望尽快做到这一点,Scala 调度程序(具体来说,ExecutionContext)将在方便时调用我们的回调。

注册回调后使用Thread.sleep()

    a.onComplete {
      case Success(x) => println(a)
      case Failure(e) => e.printStackTrace
    }
    Thread.sleep(5000)

然后正在执行未来主体的线程有时间休眠一秒钟,并将 42 设置为未来成功执行的结果。到那时(大约 1 秒后),onComplete 回调已经注册,因此线程也会调用它,您会在控制台上看到输出。

序列本质上是:

  • t = 0: 守护线程开始计算 42
  • t = 0: 主线程创建并注册回调。
  • t = 1: 守护线程完成42
  • 的计算
  • t = 1 + eps: 守护线程找到注册的回调并用结果调用它 Success(42).
  • t = 5: 主线程终止
  • t = 5 + eps: 程序停止。

(我非正式地使用 eps 作为一些相当小的时间间隔的占位符;+ eps 的意思是“几乎紧接着”。)

如果交换 a.onComplete 和外部 Thread.sleep,如

    Thread.sleep(5000)
    a.onComplete {
      case Success(x) => println(a)
      case Failure(e) => e.printStackTrace
    }

然后执行 future 主体的线程将在一秒后计算结果 42,但它不会看到任何已注册的回调(它必须再等四秒直到回调被执行)在主线程上创建和注册)。但是一旦过了 5 秒,主线程注册回调并立即退出。即使到那时它有机会知道结果 42 已经被计算出来,主线程也不会尝试执行回调,因为它是它的业务 none(这就是线程在执行上下文中是为了)。因此,在注册回调后,主线程立即退出。有了它,线程池中的所有守护线程都被杀掉,程序退出,让你在控制台看不到任何东西。

通常的事件顺序大致是这样的:

  • t = 0: 守护线程开始计算 42
  • t = 1: 守护线程完成了 42 的计算,但无法对其执行任何操作。
  • t = 5: 主线程创建并注册回调
  • t = 5 + eps: 主线程终止,守护线程被杀死,程序停止。

这样(几乎)没有时间守护线程可以唤醒、找到回调并调用它。