Spec-Zone.ru › Elixir 1.7

Агент

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

Часто в 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)

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

cast(agent, module, fun, args)

Выполняет операцию cast (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

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

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

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

cast(agent, module, fun, args)

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

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

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

child_spec(arg) (since 1.5.0)

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

См. 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.7.4/Agent.html

Spec-Zone.ru

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