线程安全访问List<属性

本文关键字:属性 List 访问 安全 线程 | 更新日期: 2023-09-27 18:19:16

我想知道这个语句是否会导致同步问题:

List<Character> characters = World.CharacterManager.Characters;

'Characters'是一个类

CharacterManager。字符'看起来像这样:

public List<Character> Characters
{
    get
    {
        lock (this.objLock) { return this.characters; }
    }
}

这会导致同步问题吗?

我想使用引用的列表来迭代找到我正在寻找的字符。

线程安全访问List<属性

问题是您在get期间锁定,但是一旦每个线程都有对集合的引用,它们就可以同时对它进行操作。由于List<T>的成员不是线程安全的,所以在迭代、添加、删除等集合时,您将遇到随机的错误和异常。

你可能需要返回一个线程安全的集合。没有一个100%兼容的线程安全版本,所以你需要查看System.Collections.Concurrent并找到一个你可以使用的版本。

这样锁就没有用了。你必须像will建议的那样使用线程安全的集合,或者如果你不需要写访问,你可以只公开列表的只读版本,如下所示:

public ReadOnlyCollection<Character> Characters {
  get {
    lock (locker) { return this.characters.AsReadOnly(); }
  }
}

这些集合不能被修改,所以如果你的Character类型是不可变的,你就不会有任何同步问题。如果Character是可变的,您将再次遇到问题,但是即使使用线程安全的集合也会遇到这个问题。我希望你知道这一点。您也可以公开返回IList<Character>的属性,但通常我发现最好告诉调用者该对象是只读的。

如果您需要写访问,您也可以通过在CharacterManager范围内提供适当的方法并同步它们来实现。Jesse写了一个很好的例子。

编辑:ICollection不存在SyncRoot

调用代码实际上需要能够从列表中添加和删除吗?如果是这样,那就不是最佳实践。这里有一个(可能的)方法来实现没有这个要求,而是把Character项的添加和删除到CharacterManager类本身:

internal sealed class CharacterManager
{
    private readonly IList<Character> characters = new List<Character>();
    public ReadOnlyCollection<Character> Characters
    {
        get
        {
            lock (this.characters)
            {
                return this.characters.AsReadOnly();
            }
        }
    }
    public void Add(Character character)
    {
        lock (this.characters)
        {
            this.characters.Add(character);
        }
    }
    public void Remove(Character character)
    {
        lock (this.characters)
        {
            this.characters.Remove(character);
        }
    }
}

如果您只希望调用者能够枚举列表,那么您的属性应该具有IEnumerable类型。如果是这种情况,那么我也会复制该列表并返回该副本。如果在枚举列表时更改了列表,那么它将无效并抛出异常。这样做的代价是调用者可能没有列表的最新版本。我倾向于将其转换为一个名为GetCharactersAsOfNow()的方法,而不是一个属性,以帮助显示调用者需要为每次调用获得更新的列表。

但是,如果您计划允许调用者修改列表,那么您必须注意列表不是线程安全的,并且要求调用者执行线程同步。既然调用者现在有了这个责任,那么你就不再需要在属性getter中使用锁了。

相关文章: