【发布时间】:2015-06-03 21:50:35
【问题描述】:
我正在制作一个小游戏,其中用户使用 WCF 发送动作,当游戏中的所有动作都发送完毕后,会模拟下一轮。
数据存储在 MSSQL 数据库中,并通过 EntityFramework 访问。
在每次调用结束时检查是否所有动作都已发送,是否应该开始模拟。
这有问题。如果两个玩家都在一个非常小的时间窗口内发送他们的动作,那么两个线程都有可能检查是否所有的动作都已经发送,找到它们并在其中一个足够远以将状态设置为模拟之前开始两个模拟(并阻止任何进一步的尝试)。
为了解决这个问题,我写了以下检查。
大致思路是这样的:
- 一个线程检查是否收到所有移动,如果它们被尝试继续进行重复检查
- 检查游戏是否处于正确状态,如果是,请在数据库中的游戏行上设置唯一的 guid。
- 等待 x 毫秒(我已将其设置为 100)
- 检查游戏中是否仍设置相同的 guid
- 如果是,请继续,如果不退出,其他人已完成此逻辑的一半,并会在他们到达时进行模拟..
我需要一些关于这是否有任何漏洞或如何改进的意见。
private void TryToRunSimulationRound(GWDatabase db, GameModel game)
{
// if we simulate, this is going to be our id while doing it
Guid simulatorGuid = Guid.NewGuid();
// force reload of game record
db.Entry(game).Reload();
// is game in right state, and have all moves been received? if so kick it in to gear!
if (game.GameState == GameState.ActiveWaitingForMoves && game.Players.Any(x => x.NextMoves == null || x.NextMoves.Count() == 0) == false)
{
// set simulation id
game.SimulatorGuid = simulatorGuid;
game.GameState = GameState.ActiveWaitingForSimulation;
db.SaveChanges();
// wait to see if someone else might be simulating
Thread.Sleep(100);
// get a new copy of the game to check against
db.Entry(game).Reload();
// if we still have same guid on the simulator then we can go ahead.. otherwise stop, someone else is running.
// this will allow the later thread to do the simulation while the earlier thread will skip it.
if (simulatorGuid == game.SimulatorGuid && game.GameState == GameState.ActiveWaitingForSimulation)
{
var s = new SimulationHandler();
s.RunSimulation(db, game.Id);
game.SimulatorGuid = null;
// wait a little in the end just to make sure we dont have any slow threads just getting to the check...
Thread.Sleep(100);
db.SaveChanges();
}
else
{
GeneralHelpers.AddLog(db, game, null, GameLogType.Debug, "Duplicate simulations stopped, game: " + game.Id);
}
}
附:这个错误花了我很长时间才弄清楚,直到我运行了一个带有 5000 个查询的System.Threading.Tasks.Parallel.ForEach,我每次都可以重现它。这种情况在现实世界中可能永远不会发生,但这是一个业余项目,玩起来很有趣;)
更新 这是胡安回答后的更新代码。这也阻止了错误的发生,并且似乎是一个更清洁的解决方案。
private static readonly object _SimulationLock = new object();
private void TryToRunSimulationRound(GWDatabase db, GameModel game)
{
bool runSimulation = false;
lock (_SimulationLock) //Ensure that only 1 thread checks to run at a time.
{
// force reload of game record
db.Entry(game).Reload();
if (game.GameState == GameState.ActiveWaitingForMoves
&& game.Players.Any(x => x.NextMoves == null || x.NextMoves.Count() == 0) == false)
{
game.GameState = GameState.ActiveWaitingForSimulation;
db.SaveChanges();
runSimulation = true;
}
}
if(runSimulation){
// get a new copy of the game to check against
db.Entry(game).Reload();
if (game.GameState == GameState.ActiveWaitingForSimulation)
{
var s = new SimulationHandler();
s.RunSimulation(db, game.Id);
db.SaveChanges();
}
}
}
【问题讨论】:
标签: c# multithreading entity-framework wcf