cnDenis http://cndenis.github.io 2014-06-20

Erlang中有两个很相似的延迟发送消息的函数, send_after/3start_timer/3, 区别仅在于前者返回Msg, 后者返回{timeout, TimerRef, Msg}. 后者的这个 TimerRef 有什么用呢?

坚强2002的博客给出了例子:

来自代码erl5.8.2\lib\stdlib-1.17.2\src\gen_fsm.erl

%% Returns Ref, sends event {timeout,Ref,Msg} after Time
%% to the (then) current state.
start_timer(Time, Msg) ->
    erlang:start_timer(Time, self(), {'$gen_timer', Msg}).

%% Returns Ref, sends Event after Time to the (then) current state.
send_event_after(Time, Event) ->
    erlang:start_timer(Time, self(), {'$gen_event', Event}).

%% Returns the remaing time for the timer if Ref referred to
%% an active timer/send_event_after, false otherwise.
cancel_timer(Ref) ->
    case erlang:cancel_timer(Ref) of
        false ->
            receive {timeout, Ref, _} -> 0
            after 0 -> false
            end;
        RemainingTime ->
            RemainingTime
    end.

代码十分清楚, 在取消定时器后, 可以利用这个Ref把消息队列中未处理的消息也删掉.

类似的用法在demonitor/1也可以看到. 取消掉一个monitor, 可以使用类似的方法, 把消息列队中的未处理的'DOWN'消息删掉:

demonitor(MonitorRef),
receive
    {_, MonitorRef, _, _, _} ->
        true
after 0 ->
        true
end

但是这样的代码不用自己写, Erlang自带的 demonitor(MonitorRef, [flush]) 就等价于上面的代码.

为什么demonitor提供了flush参数, 而cancel_timer没有呢? 因为 monitor/2 产生的消息只能发给调用它进程, 而 start_timer 产生的消息可以发给任意进程. 由于Erlang只允许读取本进程的消息队列, 不能干涉别的进程, 所以 cancel_timer 就不提供 flush 参数, 避免误解.

不过, 类似的 unlink/1 却也没有相应的 flush 参数可以用, 要自己刷消息队列:

unlink(Id),
receive
    {'EXIT', Id, _} ->
        true
after 0 ->
        true
end

呵呵.



blog comments powered by Disqus
头像

cnDenis

Email: 联系我

Fork me on GitHub