Ge

GenServer ebben a kurzusban: Elixir

4 feladat

A(z) GenServer fogalomról

A GenServer (általános szerver) egy viselkedés, amely az Elixir-folyamatok közötti gyakori kliens-szerver interakciókat absztrahálja.

Emlékszel a fogadó ciklusra, amikor a folyamatokról tanultunk? A GenServer viselkedés absztrakciókat kínál az ilyen ciklusok megvalósításához, valamint ahhoz, hogy üzeneteket váltsunk egy ilyen ciklust futtató folyamattal. Megkönnyíti az állapot megtartását és az aszinkron kód futtatását.

Note

Vigyázat, a GenServer név több dolgot is jelöl. Ezzel illetik azt a modult is, amely használja a GenServer viselkedést, valamint azt a folyamatot is, amelyet egy ilyen modul indított el.

A GenServer viselkedés egy kötelező visszahívást definiál (init/1), valamint néhány érdekes opcionális visszahívást: handle_call/3, handle_cast/2 és handle_info/3. A GenServer-t használó kliensek nem hívhatják meg ezeket a visszahívásokat közvetlenül. Ehelyett a GenServer modul olyan függvényeket kínál, amelyekkel a kliensek kommunikálhatnak egy GenServer folyamattal.

Gyakran egyetlen modul definiálja egyszerre a kliens API-t, azaz a függvények halmazát, amelyeket az Elixir-alkalmazásod más részei hívhatnak meg, hogy kommunikáljanak ezzel a GenServer folyamattal, és a szerveroldali visszahívások implementációit, amelyek a GenServer logikáját tartalmazzák.

Nézzünk először egy egyszerű példát egy GenServer-re, és utána nézzük meg, mit jelent az egyes visszahívások.

Példa

Ez egy példaszerver, amely egy hosszú autós utazás során elviseli az idegesítő utasok ismétlődő kérdezősködését, pontosabban a „Megérkeztünk már?” kérdést. Nyilvántartja, hányszor tették fel ezt a kérdést, és egyre idegesebb válaszokat ad rá.

defmodule AnnoyingPassengerAutoresponder do
  use GenServer
  # Client API

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

  def are_we_there_yet?(pid) do
    GenServer.call(pid, :are_we_there_yet?)
  end

  # Server callbacks

  @impl GenServer
  def init(_init_arg) do
    # the initial count of questions asked is always 0
    state = 0
    {:ok, state}
  end

  @impl GenServer
  def handle_call(:are_we_there_yet?, _from, state) do
    reply =
      cond do
        state <= 3 -> "No."
        state <= 10 -> "I told you #{state} times already. No."
        true -> "..."
      end

    # increase the count of questions asked
    new_state = state + 1
    # reply to the caller
    {:reply, reply, new_state}
  end
end

Visszahívások

init/1

Egy szervert a GenServer.start/3 vagy a GenServer.start_link/3 hívásával indíthatsz el. A két függvény közti különbségről a linkek fogalmánál tanultunk.

Ez a két függvény:

  • Első argumentumként egy olyan modult fogad, amely megvalósítja a GenServer viselkedést.
  • Második argumentumként bármit elfogad init_arg néven. Ahogy a neve is sugallja, ez az argumentum az init/1 visszahíváshoz kerül átadásra.
  • Elfogad egy opcionális harmadik argumentumot is a folyamat futtatásának haladó beállításaival, amelyeket most nem tárgyalunk.

Ha egy szervert a GenServer.start/3 vagy a GenServer.start_link/3 meghívásával indítasz el, az blokkoló módon hívja meg az init/1 visszahívást. Az init/1 visszatérési értéke dönti el, hogy a szerver sikeresen elindulhat-e.

Az init/1 visszahívás rendszerint az alábbi értékek egyikét adja vissza:

  • {:ok, state}. A szerver elindítja a fogadó ciklusát, és a state lesz a kezdő állapota. A state bármilyen típusú lehet.
  • {:stop, reason}. A reason bármilyen típusú lehet. A szerver nem indítja el a fogadó ciklusát. A folyamat a megadott okkal lép ki.

Vannak ennél haladóbb lehetőségek is, amelyeket most nem tárgyalunk.

Ha a szerver fogadó ciklusa elindul, a GenServer.start/3 és a GenServer.start_link/3 függvény egy {:ok, pid} tuple-t ad vissza. Ellenkező esetben {:error, reason} a visszatérési értékük

handle_call/3

Egy választ igénylő üzenetet a GenServer.call/2 segítségével küldhetsz el egy szerverfolyamatnak. Ez a függvény első argumentumként egy futó szerverfolyamat pid-jét várja, másodikként pedig az üzenetet. Az üzenet bármilyen típusú lehet.

A handle_call/3 visszahívás felelős a szinkron üzenetek kezeléséért és megválaszolásáért. Három argumentumot kap:

  1. message - a GenServer.call/2 második argumentumaként átadott érték.
  2. from - a GenServer.call/2 függvényt hívó folyamat pid-je. Legtöbbször figyelmen kívül hagyható.
  3. state - a szerver aktuális állapota. Ne feledd, hogy a kezdőértékét az init/1 visszahívásban állítottad be.

A handle_call/3 visszahívás rendszerint egy háromelemű tuple-t ad vissza: {:reply, reply, state}. Ez azt jelenti, hogy a tuple második elemét, a bármilyen típusú reply-t visszaküldi a hívónak. A tuple harmadik eleme, a state, a szerver új állapota az üzenet kezelése után.

Vannak ennél haladóbb lehetőségek is, amelyeket most nem tárgyalunk.

Note

Hogy a neve alapján megjegyezd, mit csinál ez a visszahívás, gondolj arra, mintha felhívnál valakit telefonon.

Ha az illető elérhető, azonnal (szinkron módon) választ kapsz.

handle_cast/2

Egy választ nem igénylő üzenetet a GenServer.cast/2 segítségével küldhetsz el egy szerverfolyamatnak. Az argumentumai megegyeznek a GenServer.call/2 argumentumaival.

A handle_cast/2 visszahívás felelős ezeknek az üzeneteknek a kezeléséért. Két argumentumot kap, a message-t és a state-et, amelyek ugyanazok, mint a handle_call/3 visszahívásban (a from kivételével).

A handle_cast/2 visszahívás rendszerint egy kételemű tuple-t ad vissza: {:noreply, state}.

Vannak ennél haladóbb lehetőségek is, amelyeket most nem tárgyalunk.

Note

Hogy a neve alapján megjegyezd, mit csinál ez a visszahívás, jusson eszedbe, hogy a „to cast” angol kifejezés azt is jelenti, hogy „dobni”.

Ha egy üzenetet palackba zárva a tengerbe dobsz, nem számítasz rá, hogy azonnal választ kapsz, vagy hogy egyáltalán valaha kapsz.

Melyiket használjam: a call-t vagy a cast-ot?

Szinte mindig a call-t használd, még akkor is, ha a kliensoldali kódodnak nincs szüksége a szerver válaszára.

A call használata megvárja a választ, ami visszanyomási mechanizmusként szolgál (hogy megakadályozza, hogy a kliensek egyszerre túl sok üzenetet küldjenek). A szervertől kapott válasz az egyetlen módja annak is, hogy biztos lehess benne, hogy a szerver megkapta és kezelte a kliens üzenetét.

handle_info/2

Az üzenetek más módon is a szerver postaládájába kerülhetnek, nem csak a GenServer.call/2 vagy a GenServer.cast/2 meghívásával, például a sima send/2 függvény hívásával.

Az ilyen üzenetek kezelésére a handle_info/2 visszahívást használd. Ez a visszahívás pontosan ugyanúgy működik, mint a handle_cast/2.

A GenServer viselkedés biztosít egy mindent elkapó alapértelmezett implementációt a handle_info/2-höz, amely naplózza a nem várt üzenetekkel kapcsolatos hibákat. Ha felülírod ezt az alapértelmezett implementációt, mindig gondoskodj róla, hogy a saját mindent elkapó implementációd is meglegyen. Ha elfelejted, a szerver összeomlik, amikor nem várt üzenetet kap.

Időtúllépések

A fent leírt négy visszahívás visszatérési értéke kiegészíthető még egy tuple-elemmel, egy időtúllépéssel. Például az init/1-ből {:ok, state} helyett adj vissza {:ok, state, timeout} értéket.

Az időtúllépéssel azt lehet érzékelni, hogy egy adott ideig nem érkezett üzenet a postaládába. Ha a szerver valamelyik visszahívásából időtúllépést ad vissza, és a megadott számú ezredmásodperc eltelik úgy, hogy nem érkezik üzenet, akkor a handle_info/2 hívódik meg, első argumentumként :timeout-tal.

Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg

Tanuld meg a(z) GenServer fogalmat

A gyakorlás zárolva

Oldj fel még 2 feladatot, hogy gyakorolhasd a(z) GenServer fogalmat