Выполнение функции с таймаутом - PullRequest
17 голосов
/ 14 июля 2011

Каким был бы идиоматический способ выполнения функции в течение определенного времени?Примерно так:

(with-timeout 5000
 (do-somthing))

Если только что-то не возвращается в течение 5000, выдается исключение или возвращается ноль.

РЕДАКТИРОВАТЬ: до того, как кто-то указывает на это,

clojure (with-timeout ... macro)

, но с этим будущее продолжает выполняться, что не работает в моем случае.

Ответы [ 6 ]

16 голосов
/ 14 июля 2011

Я думаю, что вы можете сделать это достаточно надежно, используя возможность тайм-аута во фьючерсах:

  (defmacro with-timeout [millis & body]
    `(let [future# (future ~@body)]
      (try
        (.get future# ~millis java.util.concurrent.TimeUnit/MILLISECONDS)
        (catch java.util.concurrent.TimeoutException x# 
          (do
            (future-cancel future#)
            nil)))))

Немного экспериментов подтвердили, что вам нужно сделать будущую отмену, чтобы не дать будущему потоку продолжитьвыполнение ....

13 голосов
/ 14 июля 2011

Это не то, что вы можете сделать на 100% надежно в JVM. Единственный способ остановить что-то через некоторое время - это дать ему новый поток, а затем отправить этому потоку исключение, когда вы хотите, чтобы это прекратилось. Но их код может перехватить исключение, или они могут раскрутить другой поток, который вы не контролируете, или ...

Но в большинстве случаев, особенно если вы контролируете тайм-аут кода, вы можете сделать что-то похожее на то, что мы делаем в clojail :

Если вы хотите сделать это красивее, вы можете определить макрос как

(defmacro with-timeout [time & body]
  `(thunk-timeout (fn [] ~@body) ~time))
8 голосов
/ 18 декабря 2014

А как же?

    (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
2 голосов
/ 18 июня 2015

Это довольно просто, используя возможности канала 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
0 голосов
/ 26 августа 2014

Добавление возможной (без макросов) альтернативы миксу (хотя макрос, конечно, не требуется в принятом ответе)

(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))

Требуются два потока, но только идея.

0 голосов
/ 14 июля 2011

Вы, вероятно, можете использовать агента , а затем await-for it.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...