Потоки, вероятно, будут самым простым решением, но не очень сложно управлять им самостоятельно в одном потоке. Среды «моделирования», которые дают вам только 100 мс, часто не допускают никаких новых потоков, поэтому это альтернатива.
Основная идея состоит в том, чтобы создать замыкание, представляющее работу, которую необходимо выполнить, чтобы завершить задачу, и вернуть ее вместо результата, если у вас нет времени, чтобы закончить. Вот эскиз: он добавляет последовательность чисел и прерывается каждые десять операций вместо каждых 100 мс.
(let [timer (atom 9)]
(defn keep-going? []
(not= 0 (swap! timer #(mod (inc %) 10)))))
(defn saving-addition [sum xs]
(if-let [[x & more] (seq xs)]
(let [next-thunk (fn [] (saving-addition (+ x sum) more))]
(if (keep-going?)
(next-thunk)
next-thunk))
sum))
(defn monitor [xs]
(loop [thunk (saving-addition 0 xs)]
(if (fn? thunk)
(do
(println "Saving execution state")
(recur (thunk)))
thunk)))
user> (monitor (range 25))
Saving execution state
Saving execution state
Saving execution state
300
Редактировать: поскольку Clojure не имеет оптимизации хвостового вызова, создание thunk и последующий вызов использует стек вверх. Если, скорее всего, вы сможете выполнить более нескольких тысяч шагов, прежде чем вам понадобится прервать работу, вы получите переполнение стека. Единственное реалистичное решение - продублировать тело thunk как в recur
, так и в продолжении, например
(defn saving-addition [sum xs]
(if-let [[x & more] (seq xs)]
(let [sum (+ x sum)]
(if (keep-going?)
(recur sum more)
#(saving-addition sum more)))
sum))
Вы могли бы, вероятно, абстрагировать это с помощью макроса, если бы вам пришлось написать несколько таких "приостановленных" функций.