将 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
}
我有一个回合制游戏的有限状态机。
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
}