我正在开发一款游戏引擎。一个angularjs前端与在IIS上运行的.NET WebAPI项目中的端点对话。这个web api是一个外观,只是运行命令反对在一个单独的项目中的游戏领域逻辑。我有一个目前正在使用实体框架的工作单元。如何在更新对象的状态时使命令同步?
因此,angularjs代码在IIS上的webapi网站中调用一个端点,该端点会创建一个针对游戏运行的命令。例如:开立银行账户。
该命令将从数据存储中加载游戏实例,检查完成并执行命令以打开银行帐户。执行命令中会发生很多事件,这些事件会更改游戏实例数据。
我有很多这些命令都具有相同的基类,但只有一个应该一次被调用。
public abstract class GameInstanceCommandHandler<T>
: CommandHandlerBase<T, GameInstanceIDCommandResult>
where T : GameInstanceCommand
{
protected GameInstance GameInstance { get; private set; }
protected GameInstanceCommandHandler() { }
public GameInstanceCommandHandler(IUnitOfWork uow)
: base(uow)
{
}
public override GameInstanceIDCommandResult Execute(T command)
{
Check.Null(command, "command");
GameInstance = UnitOfWork.GetGameInstance(command.GameInstanceID);
if (GameInstance == null)
return GetResult(false, "Could not find game instance.");
if (GameInstance.IsComplete)
{
var completeResult = GetResult(GameInstance.ID);
completeResult.Messages.Add("Game is complete, you cannot update the game state.");
completeResult.Success = false;
completeResult.IsGameComplete = true;
return completeResult;
}
UnitOfWork.LoadCollection(GameInstance, p => p.AppInstances);
var result = ExecuteCommand(command);
UnitOfWork.Save();
return result;
}
protected GameInstanceIDCommandResult GetResult(bool success, string message)
{
return new GameInstanceIDCommandResult(success, message);
}
protected GameInstanceIDCommandResult GetResult(Guid id)
{
return new GameInstanceIDCommandResult(id);
}
protected void LoadGameAndGameApps()
{
UnitOfWork.LoadReference(GameInstance, p => p.Game);
UnitOfWork.LoadCollection(GameInstance.Game, p => p.GameApps);
}
protected abstract GameInstanceIDCommandResult ExecuteCommand(T command);
}
所有的基类覆盖了抽象的ExecuteCommand。一切运行良好,我可以使用控制台项目运行我的引擎,或者在IIS中运行,或者在任何地方运行,没关系。
问题是当多个命令想要同时更改游戏实例状态时。如果我调用命令来计算游戏中银行帐户的利息,则目前对同一命令的5次调用将创建5次计算。
我想确保只有一个命令允许一次执行给定的游戏实例。由于这是一个单独的库,并不仅仅是为了在IIS进程中运行而构建的,我知道这个问题应该在这个文件中处理。我想更新下面的代码匹配:
public abstract class GameInstanceCommandHandler<T>
: CommandHandlerBase<T, GameInstanceIDCommandResult>
where T : GameInstanceCommand
{
private static readonly object @lock = new object();
private static volatile List<Guid> GameInstanceIDs = new List<Guid>();
protected GameInstance GameInstance { get; private set; }
protected GameInstanceCommandHandler() { }
public GameInstanceCommandHandler(IUnitOfWork uow)
: base(uow)
{
}
public override GameInstanceIDCommandResult Execute(T command)
{
Check.Null(command, "command");
lock(@lock)
{
// if game id is updating then return
if(GameInstanceIDs.Any(p => p == command.GameInstanceID))
return GetResult(false, "The game is already being updated.");
// (lock for update)
GameInstanceIDs.Add(command.GameInstanceID);
}
try
{
GameInstance = UnitOfWork.GetGameInstance(command.GameInstanceID);
if (GameInstance == null)
return GetResult(false, "Could not find game instance.");
if (GameInstance.IsComplete)
{
var completeResult = GetResult(GameInstance.ID);
completeResult.Messages.Add("Game is complete, you cannot update the game state.");
completeResult.Success = false;
completeResult.IsGameComplete = true;
return completeResult;
}
UnitOfWork.LoadCollection(GameInstance, p => p.AppInstances);
var result = ExecuteCommand(command);
UnitOfWork.Save();
return result;
}
catch (Exception ex)
{ }
finally
{
lock (@lock)
{
GameInstanceIDs.Remove(command.GameInstanceID);
}
}
return GetResult(false, "There was an error.");
}
protected GameInstanceIDCommandResult GetResult(bool success, string message)
{
return new GameInstanceIDCommandResult(success, message);
}
protected GameInstanceIDCommandResult GetResult(Guid id)
{
return new GameInstanceIDCommandResult(id);
}
protected void LoadGameAndGameApps()
{
UnitOfWork.LoadReference(GameInstance, p => p.Game);
UnitOfWork.LoadCollection(GameInstance.Game, p => p.GameApps);
}
protected abstract GameInstanceIDCommandResult ExecuteCommand(T command);
}
这完全解决了我的问题,但有一些问题。
- 当我在IIS中运行它时,会导致我头痛吗?
- 如果我想在许多应用程序服务器上负载均衡这个库,那么这是行不通的。
- 如果有一百万个游戏实例,那么这个列表将变得非常庞大,性能将受到影响。
另一个解决办法是通过这个锁移动到数据库:
public abstract class GameInstanceCommandHandler<T>
: CommandHandlerBase<T, GameInstanceIDCommandResult>
where T : GameInstanceCommand
{
private static readonly object @lock = new object();
protected GameInstance GameInstance { get; private set; }
protected GameInstanceCommandHandler() { }
public GameInstanceCommandHandler(IUnitOfWork uow)
: base(uow)
{
}
public override GameInstanceIDCommandResult Execute(T command)
{
Check.Null(command, "command");
lock(@lock)
{
GameInstance = UnitOfWork.GetGameInstance(command.GameInstanceID);
if (GameInstance == null)
return GetResult(false, "Could not find game instance.");
if(GameInstance.IsLocked)
return GetResult(false, "Game is locked by another command.");
// Lock the game in the database or datastore
GameInstance.Lock();
UnitOfWork.Save();
// Unlock only local copy
GameInstance.UnLock();
}
try
{
if (GameInstance.IsComplete)
{
var completeResult = GetResult(GameInstance.ID);
completeResult.Messages.Add("Game is complete, you cannot update the game state.");
completeResult.Success = false;
completeResult.IsGameComplete = true;
return completeResult;
}
UnitOfWork.LoadCollection(GameInstance, p => p.AppInstances);
var result = ExecuteCommand(command);
// this will unlock the gameinstance on the save
UnitOfWork.Save();
return result;
}
catch (Exception ex)
{ }
return GetResult(false, "There was an error.");
}
protected GameInstanceIDCommandResult GetResult(bool success, string message)
{
return new GameInstanceIDCommandResult(success, message);
}
protected GameInstanceIDCommandResult GetResult(Guid id)
{
return new GameInstanceIDCommandResult(id);
}
protected void LoadGameAndGameApps()
{
UnitOfWork.LoadReference(GameInstance, p => p.Game);
UnitOfWork.LoadCollection(GameInstance.Game, p => p.GameApps);
}
protected abstract GameInstanceIDCommandResult ExecuteCommand(T command);
}
也许我想这个错误的方式。任何帮助都会很棒。
你应该把GameInstance.Lock(); UnitOfWork.Save();里面的try块和GameInstance.UnLock();里面终于挡住了。因此,如果有任何异常情况发生,保存锁定将被释放。 –
@SouvikGhosh谢谢你,是的,我会试一试。我已经更新了我的答案。 –