2016-10-20 26 views
1

我正在开发一款游戏引擎。一个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); 
} 

这完全解决了我的问题,但有一些问题。

  1. 当我在IIS中运行它时,会导致我头痛吗?
  2. 如果我想在许多应用程序服务器上负载均衡这个库,那么这是行不通的。
  3. 如果有一百万个游戏实例,那么这个列表将变得非常庞大,性能将受到影响。

另一个解决办法是通过这个锁移动到数据库:

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); 
} 

也许我想这个错误的方式。任何帮助都会很棒。

+0

你应该把GameInstance.Lock(); UnitOfWork.Save();里面的try块和GameInstance.UnLock();里面终于挡住了。因此,如果有任何异常情况发生,保存锁定将被释放。 –

+0

@SouvikGhosh谢谢你,是的,我会试一试。我已经更新了我的答案。 –

回答

0

我已经决定目前最好的方法是有一个ID队列。我尝试立即输入游戏实例ID,如果插入是OK,则继续。做完工作后,从列表中删除该ID。如果ID插入失败,那么它已经存在。

此修复与锁相差无几,但我依赖的是唯一的键值存储。我可能会创建一个接口和类来处理这个问题,所以如果数据库失败,我可以将失败存储在一个文件或其他地方,以确保在稍后清除id。

我绝对有更多的建议,因为它确实有一个不好的代码味道。

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"); 

     try 
     { 
      AddCommandInstance(command.GameInstanceID); 
     } 
     catch(Exception ex) 
     { 
      return GetResult(false, "Only one command can be ran on an instance at a time."); 
     } 

     GameInstance = UnitOfWork.GameInstances.FirstOrDefault(p => p.ID == command.GameInstanceID); 

     if (GameInstance == null) 
     { 
      RemoveCommandInstance(command.GameInstanceID); 
      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; 
      RemoveCommandInstance(command.GameInstanceID); 
      return completeResult; 
     } 

     UnitOfWork.LoadCollection(GameInstance, p => p.AppInstances); 

     GameInstanceIDCommandResult result = null; 
     try 
     { 
      result = ExecuteCommand(command); 
      UnitOfWork.Save(); 
     } 
     catch(Exception ex) 
     { 
      result = GetResult(false, ex.Message); 
     } 
     finally 
     { 
      RemoveCommandInstance(command.GameInstanceID); 
     } 
     return result; 
    } 

    private void AddCommandInstance(Guid gameInstanceID) 
    { 
     UnitOfWork.Add(new CommandInstance() { ID = gameInstanceID }); 
     UnitOfWork.Save(); 
    } 

    private void RemoveCommandInstance(Guid gameInstanceID) 
    { 
     UnitOfWork.Remove(UnitOfWork.CommandInstances.First(p => p.ID == gameInstanceID)); 
     UnitOfWork.Save(); 
    } 

    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); 
} 
+0

我在想什么 - 你可以尝试独立地在一个单独的线程中执行锁定块。它可能会影响共享资源,但有办法照顾。因此,这将保持邮件线程免费,每次一个新的线程将独立工作。您可能还必须创建一个线程池。 –

相关问题