我知道在STM事务中放置具有副作用的函数通常是不好的做法,因为它们可能会被重试并多次调用。
但是我发现你可以使用代理来确保只有在事务成功完成后才能执行副作用。
e.g。
(dosync
// transactional stuff
(send some-agent #(function-with-side-effects params))
// more transactional stuff
)
这是好习惯吗?
有哪些优点/缺点/陷阱?
答案 0 :(得分:7)
<强>原始强>
似乎应该对我有用。根据您的副作用,您可能希望使用send-off(对于IO绑定的操作)而不是send(对于cpu绑定的操作)。发送/发送将把任务排入其中一个内部代理程序执行程序池(对于cpu有一个固定大小的池,对于io ops有一个无限大小的池)。一旦任务入队,该工作将脱离dosync的线程,因此您在此时断开连接。
当然,您需要将事务中所需的任何值捕获到已发送的函数中。并且您需要处理由于重试而可能多次发生的发送。
更新(请参阅评论):
在ref的事务中发送的代理发送将一直保持到ref事务成功完成并执行一次。因此,在我上面的回答中,发送不会多次发生,但是在ref事务期间不会发生,这可能不是你想要的(如果你希望记录或做有效的东西)。
答案 1 :(得分:5)
这是有效的,也是常见做法。然而,就像亚历克斯正确地指出你应该考虑发送一样。
有更多方法可以捕获提交值并将其移出事务。例如,您可以在向量(或地图或其他)中返回它们。
(let [[x y z] (dosync
; do stuff
[@x @y @z])] ; values of interest to sode effects
(side-effect x y z))
或者你可以调用重置!在本地原子上(当然在dosync块的词法范围之外定义)。
答案 2 :(得分:1)
使用代理没有任何问题,但只需从副作用计算所需的事务值返回就足够了。
Refs可能是最干净的方法,但你甚至可以用原子来管理它!
(def work-queue-size (atom [0]))
(defn add-job [thunk]
(let [[running accepted?]
(swap! work-queue-size
(fn [[active]]
(if (< active 3)
[(inc active) true]
[active false])))]
(println
(str "Your job has been "
(if accepted?
"queued, and there are "
"rejected - there are already ")
running
" total running jobs"))))
swap!
可以根据需要重试多次,但工作队列永远不会超过三个,并且您将始终打印一次正确绑定到的消息接受你的工作项目。 “原始设计”只需要原子中的一个int,但是你可以把它变成一对,以便将有趣的数据从计算中传回来。