2016-02-12 27 views
5

So che posso chiamare un GenServer come questoCome posso eseguire una chiamata al processo corrente in un GenServer?

GenServer.call(pid, request) 
# using a pid 

o come questo

GenServer.call(registered_name, request) 
# if I registered the process with a name 

Ma c'è un modo per excute il GenServer.call all'interno del modulo senza conoscere il nome pid/registrato? (Vale a dire c'è qualcosa di simile GenServer.call(__CURRENT_PROCESS__, request)?)

risposta

4

non sono sicuro che è il modo migliore per andare, ma quello che stai cercando è Kernel.self/0

EDIT:

commento di Per Sasha il codice sarebbe simile a questa:

GenServer.call(self, request) 

EDIT:

Per il punto eccellente fatto da Pawel Obrok, non si dovrebbe mai fare una chiamata al processo in corso . Se è necessario inviare un messaggio al processo in corso si dovrebbe fare in questo fasion:

GenServer.cast(self, request) 

NB è colata vs. chiamata.

+0

Come ha detto Onorio, è possibile utilizzare GenServer.call (self, request). Dai uno sguardo a: http://elixir-lang.org/getting-started/processes.html o la guida Little Elixir e OTP se vuoi dare un'occhiata più approfondita ai processi e alla piattaforma OTP. –

+0

@Onorio Perché non è una buona idea? – user3790827

+0

@ user3790827 Non ho detto che non è una buona idea. Ho detto che non sono sicuro che sia il modo migliore per andare. È davvero difficile da dire dato che non ho praticamente alcun contesto alla tua domanda. –

5

Questo semplicemente non funzionerà. Un GenServer.call invia semplicemente un messaggio al processo specificato e quindi attende un altro messaggio (la risposta), bloccando il processo corrente. Se si invia un messaggio a self in questo modo il processo non sarà libero di gestire quel messaggio in quanto verrà bloccato in attesa di una risposta. Quindi lo call scadrà sempre.

Quello che sospetto sia necessario è solo estrarre la funzionalità desiderata in una funzione e chiamarla direttamente. In alternativa puoi inviare un cast al processo corrente, dicendo in modo efficace di fare qualcosa "dopo".

+0

Obrok Sto utilizzando la chiamata per recuperare i dati. Ricordo di aver letto da qualche parte che è consigliabile utilizzare 'cast' quando si inviano dati a un processo senza la necessità di risposta e' call' quando si desidera effettivamente recuperare i dati dall'altro processo. – user3790827

+0

Obrok In realtà l'ho letto nella Guida introduttiva di Elixir [qui] (http://elixir-lang.org/getting-started/mix-otp/genserver.html#call-cast-or-info) – user3790827

+0

' call' viene effettivamente utilizzato per recuperare i dati ma non dal processo corrente. Se ti trovi in ​​un 'GenServer', è probabile che tu sia in una chiamata' handle_call', 'handle_cast' o' handle_info' - in tutti questi hai accesso allo stato di 'GenServer' quindi non ci dovrebbe essere motivo per richiedere ulteriormente dallo stesso processo. –

1

Da quello che ho capito dal tuo commento stai provando a scrivere una funzione API pubblica per il tuo GenServer invece di fare un call all'interno del processo GenServer. AFAIK non c'è un modo per farlo senza conoscere il PID. Tuttavia, per GenServers di cui si crea solo un'istanza, esiste un idioma per un caso del genere: si nomina l'unica istanza del proprio GenServer con il nome del modulo che la implementa. Questo può essere fatto facilmente usando il __MODULE__ macro:

def start_link do 
    GenServer.start_link(__MODULE__, nil, name: __MODULE__) 
end 

E poi nelle funzioni API è sufficiente utilizzare __MODULE__ al posto di PID:

def api_function do 
    GenServer.call(__MODULE__, :api_function) 
end 

Questo ha la simpatica proprietà che __MODULE__ sarà sempre riflettere il nome corretto del modulo che lo contiene, anche quando lo si rinomina.

1

Dipende.Se si avvia un solo GenServer processo per nodo si può chiamare come:

@doc """ 
If you want to call the server only from the current module. 
""" 
def local_call(message) do 
    GenServer.call(__MODULE__, message) 
end 

o

@doc """ 
If you want to call the server from another node on the network. 
""" 
def remote_call(message, server \\ nil) do 
    server = if server == nil, do: node(), else: server 
    GenServer.call({__MODULE__, server}, message) 
end 

Se si dispone di numerosi processi dello stesso modulo, è necessario un identificatore supplementare (Per esempio, se si dispone di una strategia di supervisore :simple_one_for_one per generare GenServer s su richiesta).

Per una cosa del genere mi consiglia di utilizzare:

  • :gproc ai processi di nome.
  • :ets se sono necessarie ulteriori informazioni per identificare i processi.

:gproc è fantastico e funziona con GenServer. Di solito nominali i tuoi processi usando un atomo come nome registrato. :gproc consente di denominare i processi con un termine arbitrario, ad esempio una tupla.

Diciamo che nella mia chiamata di funzione ho un identificatore complesso del mio server come {:id, id :: term} dove id può essere una stringa, ad esempio. Potrei iniziare la mia GenServer come:

defmodule MyServer do 
    use GenServer 

    def start_link(id) do 
    name = {:n, :l, {:id, id}} 
    GenServer.start_link(__MODULE__, %{}, name: {:via, :gproc, name}) 
    end 
    (...) 
end 

E guarda il mio processo con qualcosa di diverso da un atomo, come ho detto prima, una stringa per esempio. Quindi, se ho iniziare il mio server come:

MyServer.start_link("My Random Hash") 

E ho una funzione come:

def f(id, message) do 
    improved_call(id, message) 
end 

defp improved_call(id, message, timeout \\ 5000) do 
    name = {:n, :l, {:id, id}} 
    case :gproc.where(name) do 
    undefined -> :error 
    pid -> GenServer.call(pid, message, timeout) 
end 

È possibile utilizzarlo per chiamare processi come:

MyServer.f("My Random Hash", {:message, "Hello"}) 

Se il processo di denominazione è più complesso, è possibile utilizzare :ets per memorizzare uno stato più complesso.

Se si desidera utilizzare :gproc è possibile aggiungere al file mix.exs come:

(...) 
defp deps do 
    [{:gproc, github: "uwiger/gproc"}] 
end 
(...) 

Una nota a parte, mai, mai chiamare GenServer.call/3 dall'interno handle_call/3. Timeout e eseguirà un DOS sull'altro GenServer.call s. handle_call/3 gestisce un messaggio alla volta.