Поделиться через


Выращивание зерна

Перед написанием кода для реализации класса зерна создайте проект библиотеки классов, предназначенный для .NET Standard или .NET Core (предпочтительный) или .NET Framework 4.6.1 или более поздней версии (если вы не можете использовать .NET Standard или .NET Core из-за зависимостей). Интерфейсы и классы зерна можно определить в одном проекте библиотеки классов или в двух разных проектах для лучшего разделения интерфейсов от реализации. В любом случае проекты должны ссылаться на пакет NuGet Microsoft.Orleans.Sdk.

Дополнительные подробные инструкции см. в разделе "Настройка проекта" учебника . Orleans основы.

Зерновые интерфейсы и классы

Зерна взаимодействуют друг с другом и вызываются извне путем вызова методов, объявленных как часть соответствующих интерфейсов зерна. Класс зерна реализует один или несколько ранее объявленных интерфейсов зерна. Все методы интерфейса зерна должны возвращать Task (для методов void), Task<TResult> или ValueTask<TResult> (для методов, возвращающих значения типа T).

Ниже приведен фрагмент из примера службы присутствия Orleans:

public interface IPlayerGrain : IGrainWithGuidKey
{
    Task<IGameGrain> GetCurrentGame();

    Task JoinGame(IGameGrain game);

    Task LeaveGame(IGameGrain game);
}

public class PlayerGrain : Grain, IPlayerGrain
{
    private IGameGrain _currentGame;

    // Game the player is currently in. May be null.
    public Task<IGameGrain> GetCurrentGame()
    {
       return Task.FromResult(_currentGame);
    }

    // Game grain calls this method to notify that the player has joined the game.
    public Task JoinGame(IGameGrain game)
    {
       _currentGame = game;

       Console.WriteLine(
           $"Player {GetPrimaryKey()} joined game {game.GetPrimaryKey()}");

       return Task.CompletedTask;
    }

   // Game grain calls this method to notify that the player has left the game.
   public Task LeaveGame(IGameGrain game)
   {
       _currentGame = null;

       Console.WriteLine(
           $"Player {GetPrimaryKey()} left game {game.GetPrimaryKey()}");

       return Task.CompletedTask;
   }
}

Время ожидания ответа для методов зерна

Среда выполнения Orleans позволяет задавать время ожидания ответа для каждого метода зерна. Если метод для объектов, как "зерно," не завершается в течение заданного времени ожидания, среда выполнения генерирует исключение TimeoutException. Чтобы установить таймаут ожидания ответа, добавьте ResponseTimeoutAttribute в определение метода грейна интерфейса. Очень важно, чтобы атрибут был добавлен в определение метода интерфейса, а не в реализацию метода в классе Grain, так как и клиент, и силос должны быть осведомлены о времени ожидания.

При расширении предыдущей реализации PlayerGrain, следующий пример демонстрирует, как установить время ожидания ответа для метода LeaveGame.

public interface IPlayerGrain : IGrainWithGuidKey
{
    Task<IGameGrain> GetCurrentGame();

    Task JoinGame(IGameGrain game);

    [ResponseTimeout("00:00:05")] // 5s timeout
    Task LeaveGame(IGameGrain game);
}

Предыдущий код задает время ожидания отклика в течение пяти секунд в методе LeaveGame. Если выход из игры занимает более пяти секунд, выбрасывается TimeoutException.

Настройка времени ожидания ответа

Подобно таймаутам ответа для индивидуальных методов зерна, можно задать время ожидания ответа по умолчанию для всех методов зерна. Сообщения к "grain" методам будут завершаться по таймауту, если ответ не будет получен в течение указанного времени. По умолчанию этот период составляет 30 секунд. Вы можете настроить время ожидания ответа по умолчанию:

Дополнительные сведения о настройке Orleansсм. в конфигурации клиента или в конфигурации сервера .

Возврат значений из методов зерна

Метод зерна, который возвращает значение типа T, определяется в интерфейсе зерна как возвращающий значение типа Task<T>. Для методов работы с зерном, не помеченных ключевым словом async, если возвращаемое значение доступно, оно обычно возвращается через следующую инструкцию:

public Task<SomeType> GrainMethod1()
{
    return Task.FromResult(GetSomeType());
}

Метод зерна, который не возвращает значения, фактически метод void, определяется в интерфейсе зерна как возвращающий Task. Возвращенный Task указывает асинхронное выполнение и завершение метода. Для методов обработки зерна, не помеченных ключевым словом async, когда метод void завершает выполнение, нужно вернуть специальное значение Task.CompletedTask.

public Task GrainMethod2()
{
    return Task.CompletedTask;
}

Метод "grain", помеченный как async, возвращает значение напрямую:

public async Task<SomeType> GrainMethod3()
{
    return await GetSomeTypeAsync();
}

Метод void зерна, помеченный как async, который не возвращает значение, просто завершает выполнение:

public async Task GrainMethod4()
{
    return;
}

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

public Task<SomeType> GrainMethod5()
{
    Task<SomeType> task = CallToAnotherGrain();

    return task;
}

Аналогичным образом метод void зерна может возвращать Task, возвращенный другим вызовом, а не ожидать его.

public Task GrainMethod6()
{
    Task task = CallToAsyncAPI();
    return task;
}

ValueTask<T> можно использовать вместо Task<T>.

Ссылки на зерна

Ссылка на зерно — это прокси-объект, реализующий тот же интерфейс зерна, что и соответствующий класс зерна. Он инкапсулирует логическое удостоверение (тип и уникальный ключ) целевого зерна. Ссылки на зерна используются для вызова целевого зерна. Каждая ссылка на зерно ссылается на одно зерно (один экземпляр класса зерна), но можно создать несколько несвязанных ссылок на одно и то же зерно.

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

Ссылку на зерно можно получить путем передачи идентификатора зерна методу IGrainFactory.GetGrain<TGrainInterface>(Type, Guid), где T — интерфейс зерна, а key — уникальный ключ зерна в типе.

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

Изнутри класса grain:

IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);

Из кода клиента Orleans.

IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);

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

Вызов метода Grain

Модель программирования Orleans основана на асинхронном программировании. Используя ссылку на зерно из предыдущего примера, вызов метода зерна осуществляется следующим образом:

// Invoking a grain method asynchronously
Task joinGameTask = player.JoinGame(this);

// The await keyword effectively makes the remainder of the
// method execute asynchronously at a later point
// (upon completion of the Task being awaited) without blocking the thread.
await joinGameTask;

// The next line will execute later, after joinGameTask has completed.
players.Add(playerId);

Можно присоединить два или более Tasks; Операция соединения создает новый Task, который разрешается при завершении всех его составляющих Task. Это полезный шаблон, когда зерно должно запускать несколько вычислений и ждать завершения всех из них перед продолжением. Например, интерфейсное зерно, которое создает веб-страницу из многих частей, может выполнять несколько внутренних вызовов, по одному для каждой части, и получать Task для каждого результата. Затем зерно будет ожидать соединения всех этих Tasks; когда соединение Task будет разрешено, отдельные Taskбудут завершены, и все данные, необходимые для форматирования веб-страницы, будут получены.

Пример:

List<Task> tasks = new List<Task>();
Message notification = CreateNewMessage(text);

foreach (ISubscriber subscriber in subscribers)
{
    tasks.Add(subscriber.Notify(notification));
}

// WhenAll joins a collection of tasks, and returns a joined
// Task that will be resolved when all of the individual notification Tasks are resolved.
Task joinedTask = Task.WhenAll(tasks);

await joinedTask;

// Execution of the rest of the method will continue
// asynchronously after joinedTask is resolve.

Распространение ошибок

При возникновении исключения метод зерна Orleans передает это исключение вверх по стеку вызовов и, при необходимости, между узлами. Чтобы это работало должным образом, исключения должны быть сериализуемыми для Orleans, а хосты, обрабатывающие исключение, должны иметь доступный тип исключения. Если тип исключения недоступен, исключение будет создано как экземпляр Orleans.Serialization.UnavailableExceptionFallbackException, сохраняя сообщение, тип и трассировку стека исходного исключения.

Исключения, выбрасываемые из методов grain, не приводят к деактивации grain, если исключение не наследуется от Orleans.Storage.InconsistentStateException. InconsistentStateException выбрасывается операциями хранения, которые обнаруживают, что состояние в памяти не соответствует состоянию в базе данных. Помимо особой обработки InconsistentStateException, это поведение аналогично выбрасыванию исключения для любого объекта .NET: исключения не вызывают уничтожения объекта.

Виртуальные методы

Класс зерно может при желании переопределить виртуальные методы OnActivateAsync и OnDeactivateAsync; среда выполнения Orleans вызывает эти методы при активации и деактивации каждого экземпляра этого класса. Это дает коду зерна возможность выполнять дополнительные операции инициализации и очистки. Исключение, выбрасываемое OnActivateAsync, приводит к сбою процесса активации.

Хотя OnActivateAsync, если переопределено, всегда вызывается как часть процесса активации зерна, вызов OnDeactivateAsync не гарантирован во всех ситуациях, например, в случае сбоя сервера или другого аномального события. Из-за этого приложения не должны полагаться на OnDeactivateAsync для выполнения критически важных операций, таких как сохранение изменений состояния. Они должны использовать его только для операций с лучшими усилиями.

См. также