0

我曾多次遇到这样的情况,期货中的异常很难追踪(并且已经在这里问了一个问题,为什么某些异常似乎永远不会发生,这个问题不是我的旧问题的欺骗)并决定尝试设置一个“默认未捕获的异常处理程序”。

但是我不能让它工作。我尝试使用 reify 并尝试使用代理。就好像什么事都没发生一样。

这是重现该问题的最小案例:

REPL> (Thread/setDefaultUncaughtExceptionHandler
  (proxy [Thread$UncaughtExceptionHandler] []
    (uncaughtException [thread throwable]
      (do (println (-> throwable .getCause .getMessage))
          ; (error "whatever...") ; some 'timbre' logging if you have timbre
      ))))
nil
REPL> (future (do (Thread/sleep 100) (/ 0 0)))
#<core$future_call$reify__6267@c2909a1: :pending>
REPL>

我尝试了println,我尝试使用音色记录到一个文件,我试图吐出一个临时文件,我试图通过 deref'ing 来强制运行未来......显然,从未调用过默认的未捕获异常处理程序。

任何人都可以向我展示一个交互式/REPL 示例,说明一个工作的默认未捕获异常处理程序实际上捕获了一个异常吗?

作为一个额外的问题:一旦设置了默认的未捕获异常处理程序,是否有一种简单的方法可以“看到”它已设置?当您测试该功能并多次调用 setDefaultUncaughtExceptionHandler 时,REPL 中会发生什么?是否只考虑最后一个处理程序?

4

1 回答 1

2

期货并不是这样运作的。异常不会逃脱。

 (deref (future (do (Thread/sleep 100) (/ 0 0))))

 ExecutionException java.lang.ArithmeticException: Divide by zero
    java.util.concurrent.FutureTask.report (FutureTask.java:122)
    java.util.concurrent.FutureTask.get (FutureTask.java:192)
    clojure.core/deref-future (core.clj:2108)
    clojure.core/future-call/reify--6267 (core.clj:6308)
    clojure.core/deref (core.clj:2128)

Java 的 FutureTask.run() 的核心是答案......

boolean ran;
try {
    result = c.call();
    ran = true;
} catch (Throwable ex) {
    result = null;
    ran = false;
    setException(ex);
}

这为未来响应的最终消费者节省了问题。如果我们稍微重构一下测试用例,我们可以清楚地看到异常处理程序的工作:

(Thread/setDefaultUncaughtExceptionHandler
    (proxy [Thread$UncaughtExceptionHandler] []
      (uncaughtException [thread throwable]
        (do (println "****" (-> throwable .getMessage))
        ))))

我删除了 getCause 因为我们不一定有一个......

(.start (Thread. #(/ 0 0)))

标准输出的产量...

**** Divide by zero

如果您所追求的(如您的评论所建议的)是具有适当异常处理程序的线程,您是否考虑过类似以下内容:

(defn exception-safe-queue-reader [source sink]
    (try
        (let [message (source)]
            (sink message))
        (catch Throwable t
            (println "****" t))
    (recur source sink))

这(对我而言)是比使用 uEH 更标准的 Java 习惯用法——即,如果您要处理错误,则直接在代码中处理它们。无论您是使用裸 Java 线程还是将来启动它都无关紧要。

于 2014-04-20T12:54:31.440 回答