我正在開發一款遊戲引擎。一個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謝謝你,是的,我會試一試。我已經更新了我的答案。 –