2015-12-09 2 views
1

J'ai un GenServer simple, dans lequel je souhaite créer une boucle qui appelle une fonction toutes les deux secondes:Elixir bloqué processus GenServer

defmodule MyModule do 
    use GenServer 

    def start_link(time) do 
    GenServer.start_link(__MODULE__,time) 
    end 

    #Start loop 
    def init(time) do 
    {:ok, myLoop(time)} 
    end 

    #Loop every two seconds 
    def myLoop(time) do 

    foo = bah(:someOtherProcess, {time}) 
    IO.puts("The function value was: #{foo}") 
    :timer.sleep(2000) 
    myLoop(time + 2) 
    end 
end 

Mais quand je l'appelle avec:

{:ok, myServer} =MyModule.start_link(time) 
IO.puts("Now I can carry on...") 

Je ne voir un retour de l'appel ci-dessus. C'est un peu évident, je suppose. Donc, ma question est, comment puis-je créer la boucle que je voudrais sans bloquer le processus des tâches d'exécution en aval?

Merci.

Répondre

7

Le meilleur/le plus propre pour accomplir ce que vous essayez de faire est avec Process.send_after/3. Il délègue le délai à l'ordonnanceur, pas un autre processus. Un GenServer fonctionne de la même manière qu'une boucle d'événements, alors pourquoi le réimplémenter vous-même?

2

Puisque vous appelez votre boucle dans votre fonction init, votre boucle se bloque à l'infini et le rappel init/1 ne revient jamais.

Une technique courante pour effectuer une action sur init est d'envoyer un message au GenServer et d'utiliser handle_info/2 pour effectuer une action. Vous devez vous souvenir d'inclure un crochet pour handle_info lorsque vous faites cela.

defmodule MyModule do 
    use GenServer 

    def start_link(time) do 
    {:ok, pid} = GenServer.start_link(__MODULE__,time) 
    send(pid, {:start_loop, time}) 
    {:ok, pid} 
    end 

    #Start loop 
    def init(time) do 
    {:ok, nil} 
    end 

    def handle_info({:start_loop, time}, state) do 
    myLoop(time) 
    {:noreply, state} 
    end 

    #catch all 
    def handle_info(_, state) do 
    {:noreply, state} 
    end  

    #Loop every two seconds 
    def myLoop(time) do 
    IO.puts("The function value was: #{time}") 
    :timer.sleep(2000) 
    myLoop(time + 2) 
    end 
end 
0

Il pourrait être préférable d'exécuter un autre processus qui gère la boucle de minuterie et faites envoyer un message à votre genserver quand il doit effectuer l'action. De cette façon, le processus GenEvent est généralement inactif pour gérer d'autres messages, mais il sera averti quand il devrait prendre des mesures périodiques. Vous pouvez faire cela en exécutant votre MyModule.myloop fonction dans son propre processus avec spawn_link. Un exemple de ceci est ci-dessous:

defmodule MyModule do 
    use GenServer 

    def start_link(time) do 
    GenServer.start_link(__MODULE__,time) 
    end 

    #Start loop 
    def init(time) do 
    spawn_link(&(MyModule.myloop(0, self)) 
    {:ok, nil} 
    end 

    def handle_info({:timer, time}, state) do 
    foo = bah(:someOtherProcess, {time}) 
    IO.puts("The function value was: #{foo}") 
    {:noreply, state} 
    end 
    def handle_info(_, state) do 
    {:noreply, state} 
    end 

    #Loop every two seconds 
    defp myLoop(time, parent) do 
    send(parent, {:timer, time}) 
    :timer.sleep(2000) 
    myLoop(time + 2) 
    end 
end 

Si vous n'êtes pas pris la peine au sujet de passer le temps dans le cadre du message (vous pouvez le calculer en fonction de l'état GenServer ou similaire), vous pouvez utiliser la fonction Erlang :timer.send_interval qui enverra un message chaque période similaire à la fonction myLoop ci-dessus. La documentation est ici: http://www.erlang.org/doc/man/timer.html#send_interval-2

+2

Il existe une fonction intégrée ': timer.send_interval/2' qui fera ce dont vous avez besoin sans avoir besoin d'écrire le code vous-même. –