Из-за возможного блокирования уведомления субъекта Service Fabric не запускается обработчик событий после первого вызова - PullRequest
0 голосов
/ 05 июля 2019

У меня есть 2 надежных актера, которых зовут GameActor и PlayerActor. ClientApp отправляет сообщение на PlayerActor, когда игрок делает ход. Затем PlayerActor отправляет сообщение на GameActor, чтобы указать, что было сделано движение. После вызова метод в GameActor запускает уведомление. Это уведомление обрабатывается ClientApp GameEventsHandler. ClientApp затем вызывает метод на GameActor для получения последних позиций игрока.

ClientApp -> PlayerActor.MoveTo () -> GameActor.NotifyPlayerMoved () -> Событие Fire ScoreBoardUpdated

GameEventsHandler, вызванный этим событием -> GameActor.GetLatestPlayerInfo ()

У меня проблема в следующем. При первом запуске GameEventsHandler срабатывает и пытается вызвать GameActor, как и ожидалось. GameActor получает сообщение и возвращает ожидаемый ответ. Но клиент, похоже, не получает сообщение. Похоже, что он заблокирован, так как он не генерирует ошибки и любые выходные данные Любые последующие уведомления вообще не обрабатываются обработчиком событий.

GameActor

        public async Task<IList<PlayerInfo>> GetLatestPlayerInfoAsync(CancellationToken cancellationToken)
        {
            var allPlayers = await StateManager.GetStateAsync<List<string>>("players", cancellationToken);

            var tasks = allPlayers.Select(actorName =>
            {
                var playerActor = ActorProxy.Create<IPlayerActor>(new ActorId(actorName), new Uri(PlayerActorUri));
                return playerActor.GetLatestInfoAsync(cancellationToken);
            }).ToList();

            await Task.WhenAll(tasks);

            return tasks
                .Select(t => t.Result)
                .ToList();
        }

        public async Task NotifyPlayerMovedAsync(PlayerInfo lastMovement, CancellationToken cancellationToken)
        {
            var ev = GetEvent<IGameEvents>();
            ev.ScoreboardUpdated(lastMovement);
        }

PlayerActor

        public async Task MoveToAsync(int x, int y, CancellationToken cancellationToken)
        {
            var playerName = await StateManager.GetStateAsync<string>("playerName", cancellationToken);
            var playerInfo = new PlayerInfo()
            {
                LastUpdate = DateTimeOffset.Now,
                PlayerName = playerName,
                XCoordinate = x,
                YCoordinate = y
            };

            await StateManager.AddOrUpdateStateAsync("positions", new List<PlayerInfo>() { playerInfo }, (key, value) =>
            {
                value.Add(playerInfo);
                return value;
            }, cancellationToken);

            var gameName = await StateManager.GetStateAsync<string>("gameName", cancellationToken);
            var gameActor = ActorProxy.Create<IGameActor>(new ActorId(gameName), new Uri(GameActorUri));
            await gameActor.NotifyPlayerMovedAsync(playerInfo, cancellationToken);
        }

        public async Task<PlayerInfo> GetLatestInfoAsync(CancellationToken cancellationToken)
        {
            var positions = await StateManager.GetStateAsync<List<PlayerInfo>>("positions", cancellationToken);
            return positions.Last();
        }

Клиент

        private static async Task RunDemo(string gameName)
        {
            var rand = new Random();
            Console.WriteLine("Hit return when the service is up...");
            Console.ReadLine();
            Console.WriteLine("Enter your name:");
            var playerName = Console.ReadLine();

            Console.WriteLine("This might take a few seconds...");
            var gameActor = ActorProxy.Create<IGameActor>(new ActorId(gameName), new Uri(GameActorUri));
            await gameActor.SubscribeAsync<IGameEvents>(new GameEventsHandler(gameActor));

            var playerActorId = await gameActor.JoinGameAsync(playerName, CancellationToken.None);
            var playerActor = ActorProxy.Create<IPlayerActor>(new ActorId(playerActorId), new Uri(PlayerActorUri));

            while (true)
            {
                Console.WriteLine("Press return to move to new location...");
                Console.ReadLine();

                await playerActor.MoveToAsync(rand.Next(100), rand.Next(100), CancellationToken.None);
            }
        }

GameEventHandler

        public void ScoreboardUpdated(PlayerInfo lastInfo)
        {
            Console.WriteLine($"Scoreboard updated. (Last move by: {lastInfo.PlayerName})");

            var positions = _gameActor.GetLatestPlayerInfoAsync(CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();
//this hangs

            foreach (var playerInfo in positions) // this line never gits hit
            {
                Console.WriteLine(
                    $"Position of {playerInfo.PlayerName} is ({playerInfo.XCoordinate},{playerInfo.YCoordinate})." +
                    $"\nUpdated at {playerInfo.LastUpdate}\n");
            }            

        }

Но если я оберну логику обработчика событий внутри Task.Run(), она, похоже, сработает.

            Task.Run(async () =>
                {
                    var positions = await _gameActor.GetLatestPlayerInfoAsync(CancellationToken.None);

                    foreach (var playerInfo in positions)
                    {
                        Console.WriteLine(
                            $"Position of {playerInfo.PlayerName} is ({playerInfo.XCoordinate},{playerInfo.YCoordinate})." +
                            $"\nUpdated at {playerInfo.LastUpdate}\n");
                    }
                }
            );

Полный исходный код для демонстрации здесь https://github.com/dasiths/Service-Fabric-Reliable-Actors-Demo

Уведомления AFAIK не блокируются и не являются надежными. Поэтому я не понимаю, почему моя первоначальная реализация не работает. Шаблон повторного входа здесь не применим согласно моему пониманию. Может кто-нибудь объяснить мне, что здесь происходит? Это ожидаемое поведение или ошибка?

...