Spec-Zone.ru › Elixir 1.6

Агент

Агенты — это простое абстрагирование состояния.

Часто в Elixir требуется общий или хранимый в памяти ресурс, к которому необходимо обращаться из разных процессов или в разное время в пределах одного процесса.

Модуль Agent предоставляет базовую реализацию сервера, которая позволяет получать и обновлять состояние через простой API.

Примеры

Например, в инструменте Mix, который поставляется с Elixir, нам нужно хранить набор всех задач, выполненных заданным проектом. Поскольку этот набор общий, мы можем реализовать его с помощью агента:

defmodule Mix.TasksServer do
  use Agent

  def start_link(_) do
    Agent.start_link(fn -> MapSet.new end, name: __MODULE__)
  end

  @doc "Checks if the task has already executed"
  def executed?(task, project) do
    item = {task, project}
    Agent.get(__MODULE__, fn set ->
      item in set
    end)
  end

  @doc "Marks a task as executed"
  def put_task(task, project) do
    item = {task, project}
    Agent.update(__MODULE__, &MapSet.put(&1, item))
  end

  @doc "Resets the executed tasks and returns the previous list of tasks"
  def take_all() do
    Agent.get_and_update(__MODULE__, fn set ->
      {Enum.into(set, []), MapSet.new}
    end)
  end
end

Агенты обеспечивают разделение между клиентским и серверным API (подобно GenServers). В частности, анонимные функции, переданные в Agent, выполняются внутри агента (сервера). Это различие важно, потому что вы можете захотеть избежать дорогостоящих операций внутри агента, так как они фактически заблокируют агент до завершения запроса.

Рассмотрим эти два примера:

# Compute in the agent/server
def get_something(agent) do
  Agent.get(agent, fn state -> do_something_expensive(state) end)
end

# Compute in the agent/client
def get_something(agent) do
  Agent.get(agent, &(&1)) |> do_something_expensive()
end

Первая функция блокирует агент. Вторая функция копирует все состояние клиенту, а затем выполняет операцию на клиенте. Важно рассмотреть, достаточно ли велики данные, чтобы потребовать обработки на сервере, по крайней мере, изначально, или достаточно малы, чтобы их можно было недорого отправить клиенту. Другой фактор — нужно ли обрабатывать данные атомарно: получение состояния и вызов do_something_expensive(state) вне агента означает, что состояние агента может быть обновлено в то же время. Это особенно важно в случае обновлений, так как вычисление нового состояния на клиенте вместо сервера может привести к гонкам, если несколько клиентов пытаются обновить одно и то же состояние до разных значений.

Наконец, обратите внимание, что use Agent определяет функцию child_spec/1, позволяющую поместить определённый модуль под дерево управления. Сгенерированную child_spec/1 можно настроить с помощью следующих параметров:

  • :id - идентификатор спецификации дочернего процесса, по умолчанию совпадает с текущим модулем
  • :start - способ запуска дочернего процесса (по умолчанию вызов __MODULE__.start_link/1)
  • :restart - время, когда дочерний процесс должен быть перезапущен, по умолчанию :permanent
  • :shutdown - способ завершения дочернего процесса

Например:

use Agent, restart: :transient, shutdown: 10_000

См. документацию по Supervisor для получения дополнительной информации.

Регистрация имени

Агент подчиняется тем же правилам регистрации имен, что и GenServers. Подробнее об этом читайте в документации GenServer.

О распределенных агентах

Важно учитывать ограничения распределенных агентов. Агенты предоставляют два API: один, который работает с анонимными функциями, и другой, который ожидает явное указание модуля, функции и аргументов.

В распределенной настройке с несколькими узлами API, принимающий анонимные функции, работает только в том случае, если у вызывающего (клиента) и агента одинаковая версия вызываемого модуля.

Обратите внимание, что эта проблема также возникает при выполнении «поэтапных обновлений» с агентами. Под «поэтапными обновлениями» мы понимаем следующую ситуацию: вы хотите развернуть новую версию своего программного обеспечения, выключив некоторые узлы и заменив их узлами, на которых запущена новая версия программного обеспечения. В этой настройке часть вашей среды будет иметь одну версию данного модуля, а другая часть — другую (новую) версию того же модуля.

Лучшее решение — просто использовать API с явным указанием модуля, функции и аргументов при работе с распределёнными агентами.

Горячая замена кода

Код агента можно заменить в режиме реального времени, просто передав кортеж с модулем, функцией и аргументами в инструкцию обновления. Например, представьте, что у вас есть агент с именем :sample и вы хотите преобразовать его внутреннее состояние из списка ключевых слов в карту. Это можно сделать с помощью следующей инструкции:

{:update, :sample, {:advanced, {Enum, :into, [%{}]}}}

Состояние агента будет добавлено в предоставленный список аргументов ([%{}]) в качестве первого аргумента.

Резюме

Типы

agent()

Ссылка на агент

name()

Имя агента

on_start()

Возвращаемые значения функций start*

state()

Состояние агента

Функции

cast(agent, fun)

Выполняет операцию «бросания» (fire and forget) над состоянием агента

cast(agent, module, fun, args)

Выполняет операцию «бросания» (fire and forget) над состоянием агента

child_spec(arg)

Возвращает спецификацию для запуска агента под управлением диспетчера

get(agent, fun, timeout \\ 5000)

Получает значение агента с помощью заданной анонимной функции

get(agent, module, fun, args, timeout \\ 5000)

Получает значение агента с помощью заданной функции

get_and_update(agent, fun, timeout \\ 5000)

Получает и обновляет состояние агента в одной операции с помощью заданной анонимной функции

get_and_update(agent, module, fun, args, timeout \\ 5000)

Получает и обновляет состояние агента в одной операции с помощью заданной функции

start(fun, options \\ [])

Запускает процесс агента без связей (вне дерева управления)

start(module, fun, args, options \\ [])

Запускает агент без связей с заданным модулем, функцией и аргументами

start_link(fun, options \\ [])

Запускает агент, связанный с текущим процессом, с заданной функцией

start_link(module, fun, args, options \\ [])

Запускает агент, связанный с текущим процессом

stop(agent, reason \\ :normal, timeout \\ :infinity)

Синхронно останавливает агент с заданным reason

update(agent, fun, timeout \\ 5000)

Обновляет состояние агента с помощью заданной анонимной функции

update(agent, module, fun, args, timeout \\ 5000)

Обновляет состояние агента с помощью заданной функции

Типы

agent()

agent() :: pid() | {atom(), node()} | name()

Ссылка на агент

name()

name() :: atom() | {:global, term()} | {:via, module(), term()}

Имя агента

on_start()

on_start() :: {:ok, pid()} | {:error, {:already_started, pid()} | term()}

Возвращаемые значения функций start*

state()

state() :: term()

Состояние агента

Функции

cast(agent, fun)

cast(agent(), (state() -> state())) :: :ok

Выполняет операцию «бросания» (fire and forget) над состоянием агента.

Функция fun отправляется в agent, которая вызывает функцию, передавая состояние агента. Возвращаемое значение fun становится новым состоянием агента.

Обратите внимание, что cast возвращает :ok немедленно, независимо от того, существует ли agent (или узел, на котором он должен существовать).

cast(agent, module, fun, args)

cast(agent(), module(), atom(), [term()]) :: :ok

Выполняет операцию «бросания» (fire and forget) над состоянием агента.

То же, что и cast/2, но вместо анонимной функции ожидается модуль, функция и аргументы. Состояние добавляется как первый аргумент в указанный список аргументов.

child_spec(arg)

Возвращает спецификацию для запуска агента под управлением диспетчера.

См. Supervisor.

get(agent, fun, timeout \\ 5000)

get(agent(), (state() -> a), timeout()) :: a when a: var

Получает значение агента с помощью заданной анонимной функции.

Функция fun отправляется в agent, которая вызывает функцию, передавая состояние агента. Результат вызова функции возвращается из этой функции.

timeout — целое число, большее нуля, которое задаёт количество миллисекунд, разрешённых для выполнения функции агентом и возврата результата, или атом :infinity, чтобы ожидать неопределённое время. Если результат не получен в течение заданного времени, вызов функции завершается ошибкой, и вызывающий процесс завершается.

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.get(pid, fn state -> state end)
42

get(agent, module, fun, args, timeout \\ 5000)

get(agent(), module(), atom(), [term()], timeout()) :: any()

Получает значение агента с помощью заданной функции.

То же, что и get/3, но вместо анонимной функции ожидается модуль, функция и аргументы. Состояние добавляется как первый аргумент в указанный список аргументов.

get_and_update(agent, fun, timeout \\ 5000)

get_and_update(agent(), (state() -> {a, state()}), timeout()) :: a when a: var

Получает и обновляет состояние агента в одной операции с помощью заданной анонимной функции.

Функция fun отправляется в agent, которая вызывает функцию, передавая состояние агента. Функция должна вернуть кортеж из двух элементов, первым из которых является значение для возврата (то есть значение «get»), а вторым — новое состояние агента.

timeout — целое число, большее нуля, которое определяет количество миллисекунд, разрешённых агенту для выполнения функции и возврата результата, или атом :infinity, чтобы ждать неопределённо долго. Если результат не получен в течение указанного времени, вызов функции завершается ошибкой, и вызывающая сторона завершается.

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.get_and_update(pid, fn state -> {state, state + 1} end)
42
iex> Agent.get(pid, fn state -> state end)
43

get_and_update(agent, module, fun, args, timeout \\ 5000)

get_and_update(agent(), module(), atom(), [term()], timeout()) :: any()

Получает и обновляет состояние агента в одной операции с помощью заданной функции.

То же, что и get_and_update/3, но вместо анонимной функции ожидаются модуль, функция и аргументы. Состояние добавляется как первый аргумент в заданный список аргументов.

start(fun, options \\ [])

start((() -> term()), GenServer.options()) :: on_start()

Запускает процесс агента без связей (вне дерева надзора).

См. start_link/2 для получения дополнительной информации.

Примеры

iex> {:ok, pid} = Agent.start(fn -> 42 end)
iex> Agent.get(pid, fn state -> state end)
42

start(module, fun, args, options \\ [])

start(module(), atom(), [any()], GenServer.options()) :: on_start()

Запускает агента без связей с заданным модулем, функцией и аргументами.

См. start_link/4 для получения дополнительной информации.

start_link(fun, options \\ [])

start_link((() -> term()), GenServer.options()) :: on_start()

Запускает агента, связанного с текущим процессом, с заданной функцией.

Это часто используется для запуска агента в рамках дерева надзора.

После запуска агента заданная функция fun вызывается, и её возвращаемое значение используется в качестве состояния агента. Обратите внимание, что start_link/2 не возвращает значение, пока заданная функция не вернёт результат.

Параметры

Параметр :name используется для регистрации, как описано в документации модуля.

Если присутствует параметр :timeout, агенту разрешается потратить не более заданного количества миллисекунд на инициализацию, иначе он будет завершён, и функция запуска вернёт {:error, :timeout}.

Если присутствует параметр :debug, соответствующая функция в модуле :sys будет вызвана.

Если присутствует параметр :spawn_opt, его значение будет передано в качестве параметров в подлежащий процесс, как в Process.spawn/4.

Возвращаемые значения

Если сервер успешно создан и инициализирован, функция возвращает {:ok, pid}, где pid — PID сервера. Если агент с указанным именем уже существует, функция возвращает {:error, {:already_started, pid}} с PID этого процесса.

Если заданная функция обратного вызова завершается ошибкой, функция возвращает {:error, reason}.

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.get(pid, fn state -> state end)
42

iex> {:error, {exception, _stacktrace}} = Agent.start(fn -> raise "oops" end)
iex> exception
%RuntimeError{message: "oops"}

start_link(module, fun, args, options \\ [])

start_link(module(), atom(), [any()], GenServer.options()) :: on_start()

Запускает агента, связанного с текущим процессом.

То же, что и start_link/2, но вместо анонимной функции ожидаются модуль, функция и аргументы; fun в module будет вызван с заданными аргументами args для инициализации состояния.

stop(agent, reason \\ :normal, timeout \\ :infinity)

stop(agent(), reason :: term(), timeout()) :: :ok

Синхронно останавливает агента с заданной reason.

Возвращает :ok если агент завершается с заданной причиной. Если агент завершается по другой причине, вызов завершается.

Эта функция сохраняет семантику OTP в отношении обработки ошибок. Если причина отличается от :normal, :shutdown или {:shutdown, _}, будет записан отчёт об ошибке.

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.stop(pid)
:ok

update(agent, fun, timeout \\ 5000)

update(agent(), (state() -> state()), timeout()) :: :ok

Обновляет состояние агента с помощью заданной анонимной функции.

Функция fun отправляется в agent, которая вызывает функцию, передавая состояние агента. Возвращаемое значение fun становится новым состоянием агента.

Эта функция всегда возвращает :ok.

timeout — целое число, большее нуля, которое определяет количество миллисекунд, разрешённых агенту для выполнения функции и возврата результата, или атом :infinity для неопределённого ожидания. Если результат не получен в течение указанного времени, вызов функции завершается ошибкой, и вызывающая сторона завершается.

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.update(pid, fn state -> state + 1 end)
:ok
iex> Agent.get(pid, fn state -> state end)
43

update(agent, module, fun, args, timeout \\ 5000)

update(agent(), module(), atom(), [term()], timeout()) :: :ok

Обновляет состояние агента с помощью заданной функции.

То же, что и update/3, но вместо анонимной функции ожидаются модуль, функция и аргументы. Состояние добавляется как первый аргумент в заданный список аргументов.

© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.6.6/Agent.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API