首页 > 解决方案 > 锁定列表会产生死锁

问题描述

我正在开发一个使用multi-threading的C# WPF游戏,我遇到了一个错误,当我向操场添加一个新实体时,游戏会突然停止。

我有 2 个任务运行每个刻度,使游戏正常运行。第一个是进行所有计算并运行模拟的那个。第二个用于在每个滴答中更新渲染器。

此代码在游戏开始时运行:

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 线程创建/添加实体时停止)

以下是任务执行的相关代码:

私人无效运行模拟()

/* ... */
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));
            }
}
/* ... */

私人无效更新渲染器()

/* ... */
lock (Entities)
{
    foreach (var entityList in Entities)
        foreach (GameEntity entity in entityList.Value.ToArray())
            Application.Current.Dispatcher.Invoke(() => renderer.DrawEntity(entity));
}
/* ... */

我的问题是:我该如何解决这个问题?

我是否应该制作 List 的副本并遍历它,以便 List 仅在复制时被锁定?或者我应该将实体添加过程委托给执行所有计算的线程吗?

任何帮助是极大的赞赏!

标签: c#wpfmultithreadinglistiteration

解决方案


不确定这是否能解决您的问题,但它肯定有助于控制您生成的任务。在当前正在运行的任务完成之前,不会启动新的循环。它使用awaitandTask.Delay而不是Thread.Sleep.

Task.Run(async () =>
{
    while (state != GameLevelState.Ended)
    {
        var delayTask = Task.Delay(1000 / 60);
        if (state != GameLevelState.Paused)
        {
            var task1 = Task.Run(RunSimulation);
            var task2 = Task.Run(UpdateRenderer);
            await Task.WhenAll(task1, task2);
        }
        await delayTask;
    }
});

推荐阅读