【发布时间】:2020-07-31 20:42:59
【问题描述】:
我正在开发一个使用 多线程 的 C# WPF 游戏,我遇到了一个错误,当我在向操场添加一个新实体。
我有 2 个任务运行每个刻度,使游戏正常运行。第一个是执行所有计算并运行模拟的那个。第二个用于在每个滴答中更新 renderer。
此代码在游戏开始时运行:
Task.Run(() =>
{
while (state != GameLevelState.Ended)
{
Thread.Sleep(1000 / 60);
if (state == GameLevelState.Paused)
continue;
Task.Run(RunSimulation);
Task.Run(UpdateRenderer);
}
});
问题从存储它们的实体列表开始,因为我需要使用我的两个任务来访问它,并且我还需要在单击按钮时创建一个新实体。当UI线程想要检查实体List中是否已经存在另一个相同类型的实体时,就会出现死锁。
这是获取实体的代码:
public List<GameEntity> GetGameEntities(Func<GameEntity, bool> predicate = null)
{
if (predicate == null)
predicate = (GameEntity e) => true;
List<GameEntity> selected = new List<GameEntity>();
// this is where the execution stops
lock (Entities)
{
foreach (var entityList in Entities)
selected.AddRange(entityList.Value.Where(predicate));
return selected;
}
}
因此,当我在添加实体时尝试获取相同类型的实体时,执行会在锁上停止。 我的理论是 UI 线程找不到开口,因为其他 2 个线程/任务不断锁定实体列表。 (他们只在迭代列表时锁定列表)
(我还应该指出,如果不理会这两个任务/线程基本上永远完美运行。 它仅在尝试从 UI 线程创建/添加实体时停止)
这是任务执行的相关代码:
private void RunSimulation()
/* ... */
lock (Entities)
{
foreach (var entityList in Entities.ToArray())
foreach (GameEntity entity in entityList.Value.ToArray())
if (!entity.Tick())
{
entityList.Value.Remove(entity);
Application.Current.Dispatcher.Invoke(() => renderer.RemoveEntity(entity));
}
}
/* ... */
private void UpdateRenderer()
/* ... */
lock (Entities)
{
foreach (var entityList in Entities)
foreach (GameEntity entity in entityList.Value.ToArray())
Application.Current.Dispatcher.Invoke(() => renderer.DrawEntity(entity));
}
/* ... */
我的问题是: 我该如何解决这个问题?
我是否应该复制 List 并遍历它,以便 List 仅在复制时被锁定?或者我应该将 entity-adding-process 委托给执行所有计算的线程吗?
非常感谢任何帮助!
【问题讨论】:
-
切勿在没有
await的情况下致电Task.Run。将Thread.Sleep替换为await Task.Delay。 -
我之前试过
Task.Delay,但它什么也没做。 while 尽可能快地运行,并且仅在几秒钟内代码就死了,内存使用量很大。我正在使用Thread.Sleep创建一个相对稳定的临时计时器。 -
可能是因为你没有等待它。
标签: c# wpf multithreading list iteration