Spec-Zone.ru › Elixir 1.4

Агент

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

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

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

Примеры

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

defmodule Mix.TasksServer do
  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. В частности, весь код внутри функции, переданной агенту, выполняется агентом. Это различие важно, потому что вы можете захотеть избежать дорогостоящих операций внутри агента, поскольку он фактически будет заблокирован до тех пор, пока запрос не будет выполнен.

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

# 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

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

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

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

Несколько слов о распределенных агентах

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

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

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

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

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

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

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

Состояние агента будет добавлено в заданный список в качестве первого аргумента.

Резюме

Типы

agent()

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

name()

Имя агента

on_start()

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

state()

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

Функции

cast(agent, fun)

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

cast(agent, module, fun, args)

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

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

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

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

Обратите внимание, что cast возвращает :ok немедленно, независимо от того, существует ли целевой узел или агент.

cast(agent, module, fun, args)

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

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

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

get(agent, fun, timeout \\ 5000)

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

Получает значение агента через заданную функцию.

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

Также можно указать таймаут (по умолчанию 5000).

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), а второй — новое состояние.

Также можно указать таймаут (по умолчанию 5000).

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 для получения дополнительной информации.

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

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

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

Аналогично start/2, но вместо анонимной функции ожидаются модуль, функция и аргументы.

start_link(fun, options \\ [])

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

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

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

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

Параметры

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

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

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

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

Значения возврата

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

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

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

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

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

Аналогично start_link/2, но вместо анонимной функции ожидаются модуль, функция и аргументы.

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

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

Останавливает агента с заданным reason.

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

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

update(agent, fun, timeout \\ 5000)

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

Обновляет состояние агента.

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

Также можно указать таймаут (по умолчанию 5000). Эта функция всегда возвращает :ok.

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.4.5/Agent.html

Spec-Zone.ru

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