将 await 应用于带有回调的任务

Applying await to a task with a callback

我有一个回合制游戏的有限状态机。

ProcessState 检查当前状态并执行某些操作,然后等待调用回调

private void ProcessState(GameStatus status)
        {
            switch (status.CurrentState)
            {
                case State.START_TURN:
                    DoStartTurn(status, () =>
                    {
                        status.SetCurrentState(State.WAIT_MOVE); 
                    });
                    break;

                case State.WAIT_MOVE:
                    // ...
                    break;
            }
        }

'Do' 函数发出一个事件,外部消费者最终将调用完成。

  private void DoStartTurn(GameStatus status, Action done)
        {
            OnStartTurn?.Invoke(status, done);
        }

有没有办法在异步方法中转换 ProcessState 以简化代码?

我想要如下内容

case State.START_TURN:
   await DoStartTurn(status);
   status.SetCurrentState(State.WAIT_MOVE);
break;

我的问题是将要使用 async/await 的任务概念与等待回调的发射事件放在一起。

谢谢!

编辑:

我尝试提供整个 class(对于 Unity 来说是一个 class,但它只是一个普通的 class)

using System;
using Sirenix.OdinInspector;
using UnityEngine;

namespace GenericCardEngine
{

    [ShowInInspector]
    public class Engine
    {
        [HideInInspector]
        public Action<GameStatus, Action> OnInit;
        [HideInInspector]
        public Action<GameStatus, Action> OnStartGame;
        [HideInInspector]
        public Action<GameStatus, Action> OnStartTurn;
        [HideInInspector]
        public Action<GameStatus, Action> OnReshuffle;
        [HideInInspector]
        public Action<GameStatus, Action<Move>> OnWaitMove;
        [HideInInspector]
        public Action<GameStatus, Action> OnInvalidMove;
        [HideInInspector]
        public Action<GameStatus, Action> OnEndTurn;
        [HideInInspector]
        public Action<GameStatus, Action> OnEndGame;

        [ShowInInspector]
        GameStatus gameStatus;
        
        public Engine(Config config)
        {
            gameStatus = new GameStatus(config);
        }

        public void Begin()
        {
            gameStatus.OnStateChange += ProcessState;
            ProcessState(gameStatus);
        }

        // Preferisco far ritornare o niente o la mossa dal consumer in modo da poter visualizzare l'intera FSM qui
        private void ProcessState(GameStatus status)
        {
            if (status.ShowDebugLog) Debug.Log($"Processing {status.CurrentState}");
            switch (status.CurrentState)
            {

                case State.INIT:
                    DoInit(status, () =>
                    {
                        status.SetCurrentState(State.START_GAME);
                    });
                    break;

                case State.START_GAME:
                    DoStartGame(status, () =>
                    {
                        status.SetCurrentState(State.START_TURN);
                    });
                    break;

                case State.START_TURN:
                    DoStartTurn(status, () =>
                    {
                        if (status.Deck.IsEmpty)
                        {
                            status.SetCurrentState(State.RESHUFFLE);
                            return;
                        }
                        status.SetCurrentState(State.WAIT_MOVE);
                    });
                    break;
                    
                case State.RESHUFFLE:
                    DoReshuffle(status, () =>
                    {
                        status.SetCurrentState(State.WAIT_MOVE);
                    });
                    break;

                case State.WAIT_MOVE:
                    DoWaitMove(status, (move) =>
                    {
                        MoveType type = Rules.ClassifyMove(status, move);

                        if (type == MoveType.VALID)
                        {
                            Rules.ApplyMove(status, move);
                            status.SetCurrentState(State.END_TURN);
                        }

                        if (type == MoveType.INVALID)
                        {
                            status.SetCurrentState(State.INVALID_MOVE);
                        }

                    });
                    break;

                case State.INVALID_MOVE:
                    DoInvalidMove(status, () =>
                    {
                        status.SetCurrentState(State.WAIT_MOVE);
                    });
                    break;

                case State.END_TURN:
                    DoEndTurn(status, () =>
                    {
                        status.SetCurrentState(State.START_TURN);
                    });
                    break;

                case State.END_GAME:
                    DoEndGame(status, () =>
                    {
                        status.SetCurrentState(State.EXIT);
                    });
                    break;

                case State.EXIT:
                    break;
            }
        }

        private void DoInit(GameStatus status, Action done)
        {
            OnInit?.Invoke(status, done);
        }

        private void DoStartGame(GameStatus status, Action done)
        {
            Rules.DealInitialCardsToPlayers(status);
            Rules.DealCardsToTable(status);

            OnStartGame?.Invoke(status, done);
        }

        private void DoStartTurn(GameStatus status, Action done)
        {
            status.Turn++;
            Rules.NextPlayer(status);
            Rules.DealCardsToPlayerForTurn(status);
            OnStartTurn?.Invoke(status, done);
        }

        private void DoReshuffle(GameStatus status, Action done)
        {
            Rules.Reshuffle(status);
            OnReshuffle?.Invoke(status, done);
        }

        private void DoWaitMove(GameStatus status, Action<Move> done)
        {
            OnWaitMove?.Invoke(status, done);
        }
        private void DoInvalidMove(GameStatus status, Action done)
        {
            OnInvalidMove(status, done);
        }
        private void DoEndTurn(GameStatus status, Action done)
        {
            OnEndTurn?.Invoke(status, done);
        }
        private void DoEndGame(GameStatus status, Action done)
        {
            OnEndGame?.Invoke(status, done);
        }
    }
}

您始终可以使用 TaskCompletionSource 将回调转换为 task-based 调用:

Task DoStartTurn(GameStatus status)
    {
        var tcs = new TaskCompletionSource<bool>();
        OnStartTurn?.Invoke(status, () => tcs.SetResult(true)));
        return tcs.Task;
    }

但是,如果 task-concept 一直延伸到 call-chain,代码会更优雅。在您的示例中, DoStartTurn 是否确实执行任何异步操作并不明显。或者如果发生任何异常会发生什么。或者如果没有一个事件的监听器应该发生什么。

您要求引擎客户端提供 'callbacks to callbacks',这很奇怪。看起来设计可能会更改:

  • Action 签名更改为 Func<Task>,这样您就可以适当地等待那些
  • 从这些签名中删除回调(并仅调用内联逻辑)

像这样:

class Engine
{
    // Func returning Task, so they can be awaited
    public Func<GameStatus, Task> OnInit;
    public Func<GameStatus, Task> OnStartGame;

    // make the ProcessState function async
    private async Task ProcessState(GameStatus status)
    {
        switch (status.CurrentState)
        {

            case State.INIT:
                // business logic for init game
                await DoInit(status);
                status.SetCurrentState(State.START_GAME);
                break;

            case State.START_GAME:
                await DoStartGame(status);
                status.SetCurrentState(State.START_TURN);
                break;
            // .. etc
         }
    }

    private async Task DoInit(GameStatus status)
    {
        await OnInit(status);
    }

    private async Task DoStartGame(GameStatus status)
    {
        Rules.DealInitialCardsToPlayers(status);
        Rules.DealCardsToTable(status);
        await OnStartGame(status);
    }

    // .. etc
}