我知道将具有副作用的函数放在 STM 事务中通常是不好的做法,因为它们可能会被重试和调用多次。
然而,在我看来,您可以使用代理来确保只有在事务成功完成后才会执行副作用。
例如
(dosync
// transactional stuff
(send some-agent #(function-with-side-effects params))
// more transactional stuff
)
这是好习惯吗?
有什么优点/缺点/陷阱?
我知道将具有副作用的函数放在 STM 事务中通常是不好的做法,因为它们可能会被重试和调用多次。
然而,在我看来,您可以使用代理来确保只有在事务成功完成后才会执行副作用。
例如
(dosync
// transactional stuff
(send some-agent #(function-with-side-effects params))
// more transactional stuff
)
这是好习惯吗?
有什么优点/缺点/陷阱?
原来的:
似乎这对我有用。根据您的副作用,您可能希望使用 send-off(对于 IO-bound 操作)而不是 send(对于 cpu-bound 操作)。发送/发送会将任务排入内部代理执行程序池之一(有一个固定大小的 cpu 池和无限大小的 io ops 池)。一旦任务入队,工作就脱离了 dosync 的线程,因此您此时已断开连接。
当然,您需要从事务中捕获您需要的任何值到发送函数中。而且您需要处理由于重试而可能发生多次的发送。
更新(见评论):
在 ref 的事务中发送的代理将一直保持到 ref 事务成功完成并执行一次。所以在我上面的回答中,发送不会发生多次,但是它不会在 ref 事务期间发生,这可能不是你想要的(如果你希望记录或做副作用的事情)。
这是有效的,并且是常见的做法。但是,就像 Alex 正确指出的那样,您应该考虑发送而不是发送。
有更多方法可以捕获提交的值并将它们从事务中取出。例如,您可以在矢量(或地图或其他)中返回它们。
(let [[x y z] (dosync
; do stuff
[@x @y @z])] ; values of interest to sode effects
(side-effect x y z))
或者你可以打电话重置!在本地原子上(当然是在 dosync 块的词法范围之外定义的)。
使用代理没有什么问题,但简单地从副作用计算所需的事务值返回通常就足够了。
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,但您可以将其变成一对,以便将有趣的数据从计算中传回。