我有一个模仿 gen_fsm 的 gen_server,(不要问我为什么......),gen_server:call
会导致这个 gen_server 从当前状态转移到下一个状态,如果在一定时间内没有调用 gen_server:call
,gen_server 将终止。
更具体地说,gen_server 的生命周期如下所示:
state_1 -> state_2 -> ... -> state_N -> 终止
当服务器处于state_i时,如果该服务器上没有调用gen_server,在t_i秒后,服务器将进入终止状态,当然,这是通过使用{reply, Reply, NewState来实现的, t_i}
作为 handle_call/3
的返回。
该方法的问题是,我无法从该 gen_server 检索一些信息,为此,我需要调用 gen_server:call ,这会扰乱超时。
一种可能的解决方法是将最后一次状态转移时间戳放入状态中,在每次检索调用后,将新的超时重置为适当的值,原型(prototype)如下所示:
handle_call(get_a, _From, #state{a = 1,
state = state_2,
%% this is the timestamp when the server transfered to state_2
unixtime = T1
}=S) ->
Reply = S#state.a,
NewTimeout = t_2 - (current_unixtime() - T1),
{reply, Reply, S, NewTimeout};
这样就可以得到我想要的效果了,但是很难看,有没有更好的办法呢?
最佳答案
如果您想独立于其他事件(例如调用)设置超时,最好使用定时消息。
设置超时时使用 erlang:send_after/3:
TimerRef = erlang:send_after(10000, self(), my_timeout).
您可以随时使用 erlang:cancel_timer/1 取消超时。
erlang:cancel_timer(TimerRef).
通过handle_info接收:
handle_info(my_timeout, State) ->
如果您需要多个此类超时,请使用不同的消息,或者如果您可能存在某种竞争条件并需要进一步控制,您可以使用 erlang:make_ref/0 创建唯一引用并发送类似 {Ref ,my_timeout}。
注意边缘情况 - 请记住,您可能会取消计时器,然后仍然意外收到它(因为取消时它在您的消息队列中),并且您不会使它们唯一(如上面使用引用建议的那样)您可能会期待超时,并尽早获取它,因为它是进入消息队列的上一个消息,等等(与引用一样,您可以检查它是否是最新的一组)。这些事情很容易处理,但要小心。
关于Erlang:观察 gen_server 状态而不干扰超时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33341098/