Is it wrong to use a Java future for an infinite o

2019-07-04 22:14发布

问题:

I need to implement an infinite operation in a Clojure program. I hope the syntax is reasonably clear for Java programmers too:

(defn- my-operation
    (future
        (while @continue
            (do things)
            (Thread/sleep polling-time)))

That gives me exactly what I want, the operation in a different thread so it does not block the main one, and also a pretty clear and straightforward syntax without having to deal with native Java functions which would force me to use the dot special form.

But the definition of a Java future is a "representation of the result of an asynchronous computation" and in this case, well, I'm not really doing anything with the result.

  • Is it wrong to use them this way?.
  • Is there any technical difference that should worry me compared to starting my own Thread?.
  • Is this semantically wrong?.

回答1:

This will tie up a thread in the thread pool that Clojure manages, and uses for such things. The intended use of that thread pool is for short running operations, so it's kind-of a misuse. Also, the intention behind a future is to calculate a value once so it can be dereferenced multiple times, so that's kinds-of a misuse too.

There are lots of other options for long running tasks, including using Java's Executor framework, core-async or starting your own thread.

(defonce background-thread (doto (Thread. (fn []
                                            (while @continue
                                              (do things)
                                              (Thread/sleep polling-time))))
                             (.setDaemon true)
                             (.start)))

As others have mentioned, it might be worth thinking about unhandled exceptions. Stuart Sierra's blog post is a great place to start.

The docstring for future says that it returns something you can call deref on. It is a reference type in the same way that delay or atom is, with it's own semantics of how it get's a value and while it's true you could just create a future and let it run forever, if you see a future in some code, it implies that you care about the value it produces.

(clojure.repl/doc future)
-------------------------
clojure.core/future
([& body])
Macro
  Takes a body of expressions and yields a future object that will
  invoke the body in another thread, and will cache the result and
  return it on all subsequent calls to deref/@. If the computation has
  not yet finished, calls to deref/@ will block, unless the variant of
  deref with timeout is used. See also - realized?.


回答2:

It should also be noted that Futures will swallow all exceptions that take place in them, until they're dereferenced. If you never plan on dereferencing them, be prepared for tears.

I've been bitten multiple times by this, where suddenly everything just stops working for no apparent reason. It's only later that I realize that an exception occurred that I had no idea about.


Along the same vein as what @l0st suggests, this is what I've been using for simple cases:

(defmacro thread [& body]
  `(doto (Thread. (fn [] ~@body)
         (.start))))

Everything you then pass to thread will be implicitly run in a new thread, which allows you to write:

(thread
  (while @continue
    (stuff)))

Which is basically what you had before in terms of syntax. Note though that of course this will shadow the macro with the same name if you ever decide to use Clojure.Async.

Ya, dealing with Java interop is a pain, but if you just tuck it away somewhere, it can lead to some nice custom code.