如果进程终止,是否可以执行整个 ensure 块?

Is it ok to execute the entire ensure block if the process is terminated?

今天我了解到在 Pharo 执行:

[v := 1] ensure: [self halt. v := 2]

即使我们在 halt window(!) 处放弃进程,也会最终设置 v = 2,

我觉得这值得商榷。对我来说,#ensure: 的语义意味着序列

self halt. v := 2

必须执行,无论接收方块的情况如何,不考虑参数块的逻辑。由于 #halt 的逻辑包括终止进程的事件,我发现它干扰了第二句话的 obstinate 评估。

接下来我尝试了以下操作:

[v := 1] ensure: [1 / 0. v := 2]

当弹出 ZeroDivide 异常时,我关闭了调试器,但 v 的值仍然是 2(与 #halt 相同)

最后,我评价:

[v := 1] ensure: [n := 1 / 0. v := v + n]

并在出现 ZeroDivide 异常时关闭调试器。这次 v 的值是 1 但我没有例外,因为 v + n 无法计算。换句话说,错误无声无息地继续下去。

所以我的问题是。这种行为背后的理性是什么?该过程不应该在 "normal" 情况下终止的点终止,即不涉及 #ensure: 吗?

有趣的一个。看来你的答案在于方法 BlockClosure>>valueNoContextSwitch,它被 #ensure: 调用。如果你在那里阅读评论,它说它创建了 BlockClosure>>value 的精确副本(在原始形式中),并且该副本的 return 值得到 returned,而不是 return 包含您终止的 halt 的原始块的值。所以副本被执行(显然忽略了复制的halt),即使原始文件没有完成。

我的猜测是,这是为了确保(没有双关语意)ensure: 块始终运行,但具有忽略原始块终止的意外副作用。我同意你的看法,这不仅违反直觉,而且可能不是预期的。

我想这是任何 (ANSI) 标准都没有完全定义的行为,但如果我错了,请纠正我。

其他 Smalltalks 似乎表现不同。我在 Smalltalk/X 中尝试了它,其中调试器提供了 3 个选项:"Continue"(即继续)、"Abort"(即展开)和 "Terminate"(即终止进程)。我想 "Terminate" 对应于关闭调试器时 Squeak 所做的事情。

使用 "Abort" 和 "Terminate",确保块的其余部分不执行,使用 "Continue" 则执行。我想这没关系,你会期望什么。

在中止和终止(它们都展开到相应的异常处理程序)时,它不应尝试重新评估或继续潜在的 wrong/bad/failing 确保块。

是否要继续由处理程序(基本上是调试器)选择。如果不是,那么它应该离开 ensure 块并继续执行调用链中可能位于上方的任何其他 ensure 块。

这与异常处理块的行为一致,如果在其中引发相同的异常,也不会重新评估或继续处理异常处理块的行为。在 ST/X 中,异常 类 中有明确的代码来处理这种情况,所以它肯定是有目的的,而不是副作用。

我猜这在 Squeak 中是错误的,应该告知 Squeak 开发人员。