线程安全的List <T>属性


121

我想要一个List<T>作为属性的实现,可以毫无疑问地在线程安全地使用它。

像这样:

private List<T> _list;

private List<T> MyT
{
    get { // return a copy of _list; }
    set { _list = value; }
}

似乎仍然需要返回集合的副本(克隆的),因此,如果在某个地方我们正在迭代集合并且同时设置了集合,则不会引发异常。

如何实现线程安全的集合属性?


4
使用锁,应该这样做。
2011年

可以使用IList<T>(vs List<T>)的线程安全实现吗?
格雷格,


使用BlockingCollection或ConcurrentDictionary
kumar chandraketu

您需要对属性背后的对象执行哪些操作?您可能不需要所有List<T>实现的东西吗?如果是,那么您能否提供所需的界面,而不是询问List<T>已经拥有的一切?
维克多·亚雷玛

Answers:


185

如果您以.Net 4为目标,则System.Collections.Concurrent命名空间中有一些选项

ConcurrentBag<T>在这种情况下,您可以使用List<T>


5
与List <T>相似,与Dictionary不同,ConcurrentBag接受重复项。
《光明传》

114
ConcurrentBag是无序集合,因此与List<T>它不保证排序无关。同样,您无法按索引访问项目。
RadekStromský13年

11
@RadekStromský是正确的,如果您想要一个有序的并发列表,可以尝试使用ConcurrentQueue(FIFO)ConcurrentStack(LIFO)
Caio Cunha 2013年


12
ConcurrentBag没有实现IList,并且实际上不是List的线程安全版本
Vasyl Zvarydchuk

86

即使获得最多的票数,通常也不能将其System.Collections.Concurrent.ConcurrentBag<T>作为线程安全的替代品System.Collections.Generic.List<T>(RadekStromský已经指出)未订购。

但是System.Collections.Generic.SynchronizedCollection<T>自从.NET 3.0作为框架的一部分以来已经有一个叫做的类,但是它很好地隐藏在一个地方,人们不希望它鲜为人知,并且您可能从来没有迷失过它(至少我从没干过)。

SynchronizedCollection<T>被编译为程序集System.ServiceModel.dll(它是客户端配置文件的一部分,而不是可移植类库的一部分)。

希望有帮助。


3
我哭了,这不在核心lib中:{通常,只需要一个简单的同步集合。
user2864740'1

此选项的其他有用讨论:stackoverflow.com/a/4655236/12484
乔恩·施耐德

2
它是隐藏的,因为不推荐使用,而推荐使用System.Collections.Concurrent中的类。
denfromufa

3
.net核心中不可用
denfromufa 16-10-27

2
@denfromufa看起来他们在.NET 2.0核心加入这个喜欢docs.microsoft.com/en-gb/dotnet/api/...
Cirelli94

17

我认为制作示例ThreadSafeList类很容易:

public class ThreadSafeList<T> : IList<T>
{
    protected List<T> _interalList = new List<T>();

    // Other Elements of IList implementation

    public IEnumerator<T> GetEnumerator()
    {
        return Clone().GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return Clone().GetEnumerator();
    }

    protected static object _lock = new object();

    public List<T> Clone()
    {
        List<T> newList = new List<T>();

        lock (_lock)
        {
            _interalList.ForEach(x => newList.Add(x));
        }

        return newList;
    }
}

您只需要在请求枚举器之前克隆列表即可,因此任何枚举都在处理运行时无法修改的副本。


1
这不是一个浅表克隆吗?如果T是引用类型,这会不会只是返回一个包含对所有原始对象的引用的新列表?如果真是这样,由于多个线程可以通过列表的不同“副本”访问列表对象,因此该方法仍可能导致线程问题。
Joel B

3
正确,它是浅表副本。关键是简单地拥有一个可以安全地进行迭代的克隆集(因此newList,没有添加或删除任何会使枚举数无效的项目)。
Tejs

7
_lock应该是静态的吗?
Mike Ward

4
另一个想法。此实现对于多个编写者是否是线程安全的?如果不是,则应将其称为ReadSafeList。
Mike Ward

5
@MikeWard-我不应该这样,当克隆任何实例时,所有实例都将锁定!
Josh M.

11

即使接受的答案是ConcurrentBag,我也不认为它在所有情况下都是List的真正替代,因为Radek对答案的评论说:“ ConcurrentBag是无序集合,因此与List不同,它不能保证排序。而且您不能按索引访问项目”。

因此,如果您使用.NET 4.0或更高版本,则解决方法是使用ConcurrentDictionary,将TKey用作数组索引,将TValue用作数组值。这是在Pluralsight的C#并行集合课程中推荐的替换列表的方法。ConcurrentDictionary解决了上述两个问题:索引访问和排序(我们不能依赖排序,因为它是幕后的哈希表,但是当前的.NET实现节省了元素添加的顺序)。


1
请提供-1
tyty的

我没有拒绝投票,IMO也没有理由。您说得对,但是一些答案中已经提到了这一概念。对我来说,关键是.NET 4.0中有一个我不知道的新线程安全集合。对于这种情况,不确定是否使用了手提袋或收藏袋。+1
Xaqron's

2
这个答案有几个问题:1)ConcurrentDictionary是字典,而不是列表。2)如您自己的回答所述,不能保证按顺序保存,这与您发布答案的陈述理由相矛盾。3)它链接到视频,而没有在此答案中添加相关的引号(反正可能与许可不符)。
jpmc26

current implementation如果文档未明确保证,您将无法依赖。实施可能随时更改,恕不另行通知。
维克多·亚雷玛

@ jpmc26,是的,它当然不是List的完整替代品,但是ConcurrentBag作为公认的答案是相同的-它不是List的严格替代,但可以解决。要回答您的问题,请执行以下操作:1)ConcurrentDictionary是一个字典,而不是您所需要的列表,但是列表后面有数组,可以在O(1)中索引与int为键的字典相同的字典2)是,doc()不保证顺序(即使已保留),但接受的ConcurrentBag也不能保证在多线程方案中的顺序
tytytyty

9

C#的ArrayList类有一个Synchronized方法。

var threadSafeArrayList = ArrayList.Synchronized(new ArrayList());

这将返回的任何实例的线程安全包装器IList。所有操作都需要通过包装器执行,以确保线程安全。


1
你在说什么语言
John Demetriou

Java的?我想念的几个功能之一。但这通常写为:Collections.synchronizedList(new ArrayList());
尼克,

2
假设您使用System.Collections或使用var System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList());这是有效的C#。
user2163234 '18

5

如果查看T列表的源代码(https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877),您会注意到那里有一个类(当然是内部-为什么,Microsoft,为什么?!?!)称为T的SynchronizedList。我将代码粘贴到此处:

   [Serializable()]
    internal class SynchronizedList : IList<T> {
        private List<T> _list;
        private Object _root;

        internal SynchronizedList(List<T> list) {
            _list = list;
            _root = ((System.Collections.ICollection)list).SyncRoot;
        }

        public int Count {
            get {
                lock (_root) { 
                    return _list.Count; 
                }
            }
        }

        public bool IsReadOnly {
            get {
                return ((ICollection<T>)_list).IsReadOnly;
            }
        }

        public void Add(T item) {
            lock (_root) { 
                _list.Add(item); 
            }
        }

        public void Clear() {
            lock (_root) { 
                _list.Clear(); 
            }
        }

        public bool Contains(T item) {
            lock (_root) { 
                return _list.Contains(item);
            }
        }

        public void CopyTo(T[] array, int arrayIndex) {
            lock (_root) { 
                _list.CopyTo(array, arrayIndex);
            }
        }

        public bool Remove(T item) {
            lock (_root) { 
                return _list.Remove(item);
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            lock (_root) { 
                return _list.GetEnumerator();
            }
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator() {
            lock (_root) { 
                return ((IEnumerable<T>)_list).GetEnumerator();
            }
        }

        public T this[int index] {
            get {
                lock(_root) {
                    return _list[index];
                }
            }
            set {
                lock(_root) {
                    _list[index] = value;
                }
            }
        }

        public int IndexOf(T item) {
            lock (_root) {
                return _list.IndexOf(item);
            }
        }

        public void Insert(int index, T item) {
            lock (_root) {
                _list.Insert(index, item);
            }
        }

        public void RemoveAt(int index) {
            lock (_root) {
                _list.RemoveAt(index);
            }
        }
    }

我个人认为他们知道可以使用SemaphoreSlim创建更好的实现,但是并没有实现。


2
+1 _root在每次访问(读/写)中锁定整个集合(),这是一个很慢的解决方案。最好让此类保持内部。
Xaqron '18年

3
此实现不是线程安全的。它仍然抛出“ System.InvalidOperationException:'集合已修改;枚举操作可能无法执行。'”
Raman Zhylich

2
这与线程安全无关,而是与您要迭代和更改集合的事实有关。枚举器看到列表已更改时,将引发异常。为了解决这个问题,您需要实现自己的IEnumerator或更改代码,以使其不会迭代并同时更改同一集合。
Siderite Zackwehdex

它不是线程安全的,因为可以在“同步”方法期间更改集合。那绝对线程安全的一部分。考虑Clear()在另一个调用之后this[index]但在激活锁之前一个线程调用。index不再安全使用,并且在最终执行时将引发异常。
Suncat2000

2

您还可以使用更原始的

Monitor.Enter(lock);
Monitor.Exit(lock);

哪个锁使用(请参见C#锁定在lock块中重新分配的对象)。

如果您期望代码中出现异常,这是不安全的,但是它允许您执行以下操作:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;

public class Something
{
    private readonly object _lock;
    private readonly List<string> _contents;

    public Something()
    {
        _lock = new object();

        _contents = new List<string>();
    }

    public Modifier StartModifying()
    {
        return new Modifier(this);
    }

    public class Modifier : IDisposable
    {
        private readonly Something _thing;

        public Modifier(Something thing)
        {
            _thing = thing;

            Monitor.Enter(Lock);
        }

        public void OneOfLotsOfDifferentOperations(string input)
        {
            DoSomethingWith(input);
        }

        private void DoSomethingWith(string input)
        {
            Contents.Add(input);
        }

        private List<string> Contents
        {
            get { return _thing._contents; }
        }

        private object Lock
        {
            get { return _thing._lock; }
        }

        public void Dispose()
        {
            Monitor.Exit(Lock);
        }
    }
}

public class Caller
{
    public void Use(Something thing)
    {
        using (var modifier = thing.StartModifying())
        {
            modifier.OneOfLotsOfDifferentOperations("A");
            modifier.OneOfLotsOfDifferentOperations("B");

            modifier.OneOfLotsOfDifferentOperations("A");
            modifier.OneOfLotsOfDifferentOperations("A");
            modifier.OneOfLotsOfDifferentOperations("A");
        }
    }
}

这样做的好处之一是,您将在一系列操作的持续时间内获得锁定(而不是锁定每个操作)。这意味着输出应该以正确的块显示(我的用法是从外部进程将某些输出显示到屏幕上)

我真的很喜欢ThreadSafeList的简单性和透明性,它在阻止崩溃方面起着重要的作用



1

我相信_list.ToList()会为您提供一份副本。如果需要,您还可以查询它:

_list.Select("query here").ToList(); 

无论如何,msdn说这确实是副本,而不仅仅是引用。哦,是的,您将需要锁定其他人指出的set方法。


1

似乎很多人发现他们想要一个线程安全索引的动态大小的集合。我所知道的最接近和最简单的事情就是。

System.Collections.Concurrent.ConcurrentDictionary<int, YourDataType>

如果您想要正常的索引行为,这将需要您确保对密钥进行正确的识别。如果您小心的话,.count可以作为添加的任何新键值对的键。


1
如果不是钥匙的过错,为什么要归咎于钥匙?
Suncat2000

@ Suncat2000哈!
理查二世

1

我建议任何List<T>在多线程场景中进行处理的人都应该研究Immutable Collections,尤其是ImmutableArray

当您具备以下条件时,我发现它非常有用:

  1. 列表中相对较少的项目
  2. 没有太多的读/写操作
  3. 大量并发访问(即,许多以读取模式访问列表的线程)

当您需要实现某种类似于事务的行为时(例如,在失败的情况下还原插入/更新/删除操作),该功能也很有用。


-1

这是您要求的课程:

namespace AI.Collections {
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.Threading.Tasks;
    using System.Threading.Tasks.Dataflow;

    /// <summary>
    ///     Just a simple thread safe collection.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <value>Version 1.5</value>
    /// <remarks>TODO replace locks with AsyncLocks</remarks>
    [DataContract( IsReference = true )]
    public class ThreadSafeList<T> : IList<T> {
        /// <summary>
        ///     TODO replace the locks with a ReaderWriterLockSlim
        /// </summary>
        [DataMember]
        private readonly List<T> _items = new List<T>();

        public ThreadSafeList( IEnumerable<T> items = null ) { this.Add( items ); }

        public long LongCount {
            get {
                lock ( this._items ) {
                    return this._items.LongCount();
                }
            }
        }

        public IEnumerator<T> GetEnumerator() { return this.Clone().GetEnumerator(); }

        IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }

        public void Add( T item ) {
            if ( Equals( default( T ), item ) ) {
                return;
            }
            lock ( this._items ) {
                this._items.Add( item );
            }
        }

        public Boolean TryAdd( T item ) {
            try {
                if ( Equals( default( T ), item ) ) {
                    return false;
                }
                lock ( this._items ) {
                    this._items.Add( item );
                    return true;
                }
            }
            catch ( NullReferenceException ) { }
            catch ( ObjectDisposedException ) { }
            catch ( ArgumentNullException ) { }
            catch ( ArgumentOutOfRangeException ) { }
            catch ( ArgumentException ) { }
            return false;
        }

        public void Clear() {
            lock ( this._items ) {
                this._items.Clear();
            }
        }

        public bool Contains( T item ) {
            lock ( this._items ) {
                return this._items.Contains( item );
            }
        }

        public void CopyTo( T[] array, int arrayIndex ) {
            lock ( this._items ) {
                this._items.CopyTo( array, arrayIndex );
            }
        }

        public bool Remove( T item ) {
            lock ( this._items ) {
                return this._items.Remove( item );
            }
        }

        public int Count {
            get {
                lock ( this._items ) {
                    return this._items.Count;
                }
            }
        }

        public bool IsReadOnly { get { return false; } }

        public int IndexOf( T item ) {
            lock ( this._items ) {
                return this._items.IndexOf( item );
            }
        }

        public void Insert( int index, T item ) {
            lock ( this._items ) {
                this._items.Insert( index, item );
            }
        }

        public void RemoveAt( int index ) {
            lock ( this._items ) {
                this._items.RemoveAt( index );
            }
        }

        public T this[ int index ] {
            get {
                lock ( this._items ) {
                    return this._items[ index ];
                }
            }
            set {
                lock ( this._items ) {
                    this._items[ index ] = value;
                }
            }
        }

        /// <summary>
        ///     Add in an enumerable of items.
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="asParallel"></param>
        public void Add( IEnumerable<T> collection, Boolean asParallel = true ) {
            if ( collection == null ) {
                return;
            }
            lock ( this._items ) {
                this._items.AddRange( asParallel
                                              ? collection.AsParallel().Where( arg => !Equals( default( T ), arg ) )
                                              : collection.Where( arg => !Equals( default( T ), arg ) ) );
            }
        }

        public Task AddAsync( T item ) {
            return Task.Factory.StartNew( () => { this.TryAdd( item ); } );
        }

        /// <summary>
        ///     Add in an enumerable of items.
        /// </summary>
        /// <param name="collection"></param>
        public Task AddAsync( IEnumerable<T> collection ) {
            if ( collection == null ) {
                throw new ArgumentNullException( "collection" );
            }

            var produce = new TransformBlock<T, T>( item => item, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount } );

            var consume = new ActionBlock<T>( action: async obj => await this.AddAsync( obj ), dataflowBlockOptions: new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount } );
            produce.LinkTo( consume );

            return Task.Factory.StartNew( async () => {
                collection.AsParallel().ForAll( item => produce.SendAsync( item ) );
                produce.Complete();
                await consume.Completion;
            } );
        }

        /// <summary>
        ///     Returns a new copy of all items in the <see cref="List{T}" />.
        /// </summary>
        /// <returns></returns>
        public List<T> Clone( Boolean asParallel = true ) {
            lock ( this._items ) {
                return asParallel
                               ? new List<T>( this._items.AsParallel() )
                               : new List<T>( this._items );
            }
        }

        /// <summary>
        ///     Perform the <paramref name="action" /> on each item in the list.
        /// </summary>
        /// <param name="action">
        ///     <paramref name="action" /> to perform on each item.
        /// </param>
        /// <param name="performActionOnClones">
        ///     If true, the <paramref name="action" /> will be performed on a <see cref="Clone" /> of the items.
        /// </param>
        /// <param name="asParallel">
        ///     Use the <see cref="ParallelQuery{TSource}" /> method.
        /// </param>
        /// <param name="inParallel">
        ///     Use the
        ///     <see
        ///         cref="Parallel.ForEach{TSource}(System.Collections.Generic.IEnumerable{TSource},System.Action{TSource})" />
        ///     method.
        /// </param>
        public void ForEach( Action<T> action, Boolean performActionOnClones = true, Boolean asParallel = true, Boolean inParallel = false ) {
            if ( action == null ) {
                throw new ArgumentNullException( "action" );
            }
            var wrapper = new Action<T>( obj => {
                try {
                    action( obj );
                }
                catch ( ArgumentNullException ) {
                    //if a null gets into the list then swallow an ArgumentNullException so we can continue adding
                }
            } );
            if ( performActionOnClones ) {
                var clones = this.Clone( asParallel: asParallel );
                if ( asParallel ) {
                    clones.AsParallel().ForAll( wrapper );
                }
                else if ( inParallel ) {
                    Parallel.ForEach( clones, wrapper );
                }
                else {
                    clones.ForEach( wrapper );
                }
            }
            else {
                lock ( this._items ) {
                    if ( asParallel ) {
                        this._items.AsParallel().ForAll( wrapper );
                    }
                    else if ( inParallel ) {
                        Parallel.ForEach( this._items, wrapper );
                    }
                    else {
                        this._items.ForEach( wrapper );
                    }
                }
            }
        }

        /// <summary>
        ///     Perform the <paramref name="action" /> on each item in the list.
        /// </summary>
        /// <param name="action">
        ///     <paramref name="action" /> to perform on each item.
        /// </param>
        /// <param name="performActionOnClones">
        ///     If true, the <paramref name="action" /> will be performed on a <see cref="Clone" /> of the items.
        /// </param>
        /// <param name="asParallel">
        ///     Use the <see cref="ParallelQuery{TSource}" /> method.
        /// </param>
        /// <param name="inParallel">
        ///     Use the
        ///     <see
        ///         cref="Parallel.ForEach{TSource}(System.Collections.Generic.IEnumerable{TSource},System.Action{TSource})" />
        ///     method.
        /// </param>
        public void ForAll( Action<T> action, Boolean performActionOnClones = true, Boolean asParallel = true, Boolean inParallel = false ) {
            if ( action == null ) {
                throw new ArgumentNullException( "action" );
            }
            var wrapper = new Action<T>( obj => {
                try {
                    action( obj );
                }
                catch ( ArgumentNullException ) {
                    //if a null gets into the list then swallow an ArgumentNullException so we can continue adding
                }
            } );
            if ( performActionOnClones ) {
                var clones = this.Clone( asParallel: asParallel );
                if ( asParallel ) {
                    clones.AsParallel().ForAll( wrapper );
                }
                else if ( inParallel ) {
                    Parallel.ForEach( clones, wrapper );
                }
                else {
                    clones.ForEach( wrapper );
                }
            }
            else {
                lock ( this._items ) {
                    if ( asParallel ) {
                        this._items.AsParallel().ForAll( wrapper );
                    }
                    else if ( inParallel ) {
                        Parallel.ForEach( this._items, wrapper );
                    }
                    else {
                        this._items.ForEach( wrapper );
                    }
                }
            }
        }
    }
}

我更新课程后,Google云端硬盘上的版本会更新。uberscraper.blogspot.com/2012/12/c-thread-safe-list.html
连续的

为什么this.GetEnumerator();@Tejs建议this.Clone().GetEnumerator();
2014年

为什么[DataContract( IsReference = true )]
2014年


我发现并修复了Add()方法中的两个小错误。仅供参考。
2014年

-2

这是不带锁的线程安全列表的类

 public class ConcurrentList   
    {
        private long _i = 1;
        private ConcurrentDictionary<long, T> dict = new ConcurrentDictionary<long, T>();  
        public int Count()
        {
            return dict.Count;
        }
         public List<T> ToList()
         {
            return dict.Values.ToList();
         }

        public T this[int i]
        {
            get
            {
                long ii = dict.Keys.ToArray()[i];
                return dict[ii];
            }
        }
        public void Remove(T item)
        {
            T ov;
            var dicItem = dict.Where(c => c.Value.Equals(item)).FirstOrDefault();
            if (dicItem.Key > 0)
            {
                dict.TryRemove(dicItem.Key, out ov);
            }
            this.CheckReset();
        }
        public void RemoveAt(int i)
        {
            long v = dict.Keys.ToArray()[i];
            T ov;
            dict.TryRemove(v, out ov);
            this.CheckReset();
        }
        public void Add(T item)
        {
            dict.TryAdd(_i, item);
            _i++;
        }
        public IEnumerable<T> Where(Func<T, bool> p)
        {
            return dict.Values.Where(p);
        }
        public T FirstOrDefault(Func<T, bool> p)
        {
            return dict.Values.Where(p).FirstOrDefault();
        }
        public bool Any(Func<T, bool> p)
        {
            return dict.Values.Where(p).Count() > 0 ? true : false;
        }
        public void Clear()
        {
            dict.Clear();
        }
        private void CheckReset()
        {
            if (dict.Count == 0)
            {
                this.Reset();
            }
        }
        private void Reset()
        {
            _i = 1;
        }
    }

这不是线程安全的
Aldracor

-3

基本上,如果要安全枚举,则需要使用锁。

请参考MSDN。http://msdn.microsoft.com/zh-CN/library/6sh2ey19.aspx

这是您可能感兴趣的MSDN的一部分:

此类型的公共静态(在Visual Basic中为Shared)成员是线程安全的。不保证任何实例成员都是线程安全的。

只要不修改集合,列表就可以同时支持多个阅读器。通过集合进行枚举本质上不是线程安全的过程。在极少数情况下,枚举与一个或多个写访问竞争,确保线程安全的唯一方法是在整个枚举期间锁定集合。要允许多个线程访问该集合以进行读写,您必须实现自己的同步。


2
完全不对。您可以使用并发集。
2015年

-15

使用该lock语句执行此操作。(阅读更多信息。

private List<T> _list;

private List<T> MyT
{
    get { return _list; }
    set
    {
        //Lock so only one thread can change the value at any given time.
        lock (_list)
        {
            _list = value;
        }
    }
}

仅供参考,这可能与您的要求不完全相同-您可能想在代码中进一步锁定,但我无法假设。看看lock关键字,并根据您的具体情况调整其用法。

如果需要,可以lockgetset块中都使用_list变量,该变量将使它不能同时进行读/写。


1
那不会解决他的问题。它只会阻止线程设置引用,而不会添加到列表中。
Tejs,2011年

而且,如果一个线程在设置值,而另一个线程在迭代集合(代码可能实现),该怎么办?
Xaqron

就像我说的那样,锁可能必须在代码中进一步移出。这只是如何使用lock语句的示例。
乔什·M

2
@乔尔·穆勒(Joel Mueller):当然,如果您制造了一些愚蠢的例子。我只是想说明询问者应该考虑该lock语句。使用一个类似的示例,我可能会争辩说我们不应该使用循环,因为您几乎可以毫不费力地使应用程序死锁:for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Josh M.

5
我从未声称您的代码意味着立即死锁。这个问题的答案很差,原因如下:1)它不能防止在枚举列表时或同时由两个线程修改列表的内容。2)锁定setter而不是getter意味着该属性不是真正的线程安全的。3)锁定从课堂以外可以访问的任何参考文献被广泛认为是一种不好的做法,因为这会大大增加意外死锁的可能性。这就是为什么lock (this)并且lock (typeof(this))是大禁忌。
乔尔·穆勒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.