日常踩坑之gen_server的不同方式处理stop消息


希望将正在运行的gen_server停止的时候,可以通过向gen_server发送stop消息来停止gen_server,有以下几种方式:

1.gen_server:call(Pid,stop):通过gen_server的API,可以看到,该方式可以通过返回{stop,Reason,State}{stop,Reason,Reply,State}两种返回方式,这两种方式存在一定的差异,如下:

handle_call({stop, 1}, _From, State) ->
  {stop, normal, "stoped", State};
handle_call({stop, 2}, _From, State) ->
  {stop, normal, State};

运行测试代码:

(test@192.168.101.13)4> test_stop:start_link().
{ok,<0.90.0>}
(test@192.168.101.13)5> test_stop:stop(call1). 
"stoped"
(test@192.168.101.13)6> test_stop:start_link().
{ok,<0.93.0>}
(test@192.168.101.13)7> test_stop:stop(call2). 
** exception exit: {normal,{gen_server,call,[<0.93.0>,{stop,2}]}}
     in function  gen_server:call/2 (gen_server.erl, line 215)

结论:4元组的返回,会把第三个元素作为call的返回值返回给调用方,而3元组的返回,则会报一个exit的错误,从而导致没有正常返回,所以,如果希望调用方能收到正常返回而不是一个报错,那么就乖乖用4元组的返回方式(别偷懒).

2.gen_server:cast(Pid,stop):cast回调函数只会返回{stop,Reason,State}来停止gen_server,而且,返回值只有ok,(从gen_server.erl中可以看出无论cast处理什么消息都只返回ok).

handle_cast(stop, State) ->
  {stop, normal, State};

测试结果:

(test@192.168.101.13)8> test_stop:start_link().
{ok,<0.97.0>}
(test@192.168.101.13)9> test_stop:stop(cast).
ok
(test@192.168.101.13)10> test_stop:stop(cast).
no server is exist
ok

3.Pid ! stop: 通过gen_server的handle_info回调来停止gen_server,返回{stop,Reason,State}

handle_info(stop_info, State) ->
  {stop, normal, State};

测试结果:

(test@192.168.101.13)11> test_stop:start_link().
{ok,<0.101.0>}
(test@192.168.101.13)12> test_stop:stop(info).
stop_info

注意:此处最终的返回打印是stop_info,即:发给gen_server的停止消息.

最后附上测试的代码:

%%% 停止
stop(Msg) ->
  case whereis(?MODULE) of
    Pid when is_pid(Pid) ->
      handle_msg_(Pid, Msg);
    undefined ->
      io:format("no server is exist~n")
  end.
%%测试不同消息
handle_msg_(Pid, call1) ->
  gen_server:call(Pid, {stop, 1});
handle_msg_(Pid, call2) ->
  gen_server:call(Pid, {stop, 2});
handle_msg_(Pid, cast) ->
  gen_server:cast(Pid, stop);
handle_msg_(Pid, _) ->
  Pid ! stop_info.

总结:在使用gen_server:call(pid,stop)时,要特别注意调用方是否需要返回结果!!!,2,3两种方式正常返回即可.