线程安全访问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; }
}
}
这会导致同步问题吗?
我想使用引用的列表来迭代找到我正在寻找的字符。
问题是您在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
调用代码实际上需要能够从列表中添加和删除吗?如果是这样,那就不是最佳实践。这里有一个(可能的)方法来实现没有这个要求,而是把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中使用锁了。