Выполнение функции с таймаутом
Каким будет идиоматический способ выполнения функции в течение срока? Что-то вроде,
(with-timeout 5000
(do-somthing))
Если do-something не возвращается в течение 5000, выведите исключение или верните нуль.
РЕДАКТИРОВАТЬ: прежде чем кто-нибудь отметит это,
clojure (with-timeout... macro)
но с этим будущее продолжает выполняться, что не работает в моем случае.
Ответы
Ответ 1
Это не то, что вы можете сделать 100% надежно на JVM. Единственный способ остановить что-то через некоторое время - дать ему новый поток, а затем отправить этот поток как исключение, когда вы хотите его остановить. Но их код может поймать исключение, или они могут развернуть другой поток, который вы не контролируете, или...
Но большую часть времени, и особенно если вы контролируете время ожидания кода, вы можете сделать что-то вроде в clojail:
Если вы хотите сделать это красивее, вы можете определить макрос, например
(defmacro with-timeout [time & body]
`(thunk-timeout (fn [] [email protected]) ~time))
Ответ 2
Я думаю, что вы можете сделать это достаточно надежно, используя функцию тайм-аута в рамках фьючерсов:
(defmacro with-timeout [millis & body]
`(let [future# (future [email protected])]
(try
(.get future# ~millis java.util.concurrent.TimeUnit/MILLISECONDS)
(catch java.util.concurrent.TimeoutException x#
(do
(future-cancel future#)
nil)))))
Немного экспериментирования подтвердили, что вам нужно сделать отсрочку в будущем, чтобы остановить продолжение будущего потока....
Ответ 3
Как насчет?
(defn timeout [timeout-ms callback]
(let [fut (future (callback))
ret (deref fut timeout-ms ::timed-out)]
(when (= ret ::timed-out)
(future-cancel fut))
ret))
(timeout 100 #(Thread/sleep 1000))
;=> :user/timed-out
Ответ 4
Это довольно легкий ветер с использованием возможностей канала clojure
https://github.com/clojure/core.async
требуется соответствующее пространство имен
(:require [clojure.core.async :refer [>! alts!! timeout chan go]])
ожидание функции принимает тайм-аут [мс], функцию [f] и необязательные параметры [args]
(defn wait [ms f & args]
(let [c (chan)]
(go (>! c (apply f args)))
(first (alts!! [c (timeout ms)]))))
третья строка выталкивает вызов f в другой поток. четвертая строка потребляет результат вызова функции или (если быстрее) тайм-аут.
рассмотрим следующий пример:
(wait 1000 (fn [] (do (Thread/sleep 100) 2)))
=> 2
но
(wait 50 (fn [] (do (Thread/sleep 100) 2)))
=> nil
Ответ 5
Возможно, вы можете использовать agent, а затем await-for it.
Ответ 6
Добавление возможной (макро-меньше) альтернативы миксу (хотя макрос не требуется в принятом ответе, конечно)
(defn with-timeout [f ms]
(let [p (promise)
h (future
(deliver p (f)))
t (future
(Thread/sleep ms)
(future-cancel h)
(deliver p nil))]
@p))
Требуется два потока, но только идея.