Câu hỏi Thuộc tính danh sách an toàn


Tôi muốn thực hiện List<T> như một tài sản có thể được sử dụng thread-an toàn mà không có bất kỳ nghi ngờ.

Một cái gì đó như thế này:

private List<T> _list;

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

Có vẻ như tôi vẫn cần trả lại một bản sao (nhân bản) của bộ sưu tập vì vậy nếu ở đâu đó chúng tôi đang lặp lại bộ sưu tập và đồng thời bộ sưu tập được thiết lập, thì không có ngoại lệ nào được nêu ra.

Làm thế nào để thực hiện một thuộc tính thu thập an toàn thread?


76
2018-05-03 19:01


gốc


sử dụng ổ khóa, mà nên làm điều đó. - atoMerz
Có thể sử dụng việc triển khai chủ đề an toàn IList<T> (so với List<T>)? - Greg
Bạn coi lại chưa SynchronizedCollection <T> ? - Saturn Technologies


Các câu trả lời:


Nếu bạn đang nhắm mục tiêu .Net 4 có một vài tùy chọn trong System.Collections.Concurrent Không gian tên

Bạn đã có thể sử dụng ConcurrentBag<T> trong trường hợp này thay vì List<T>


131
2018-05-03 19:04



Giống như List <T> và không giống như Dictionary, ConcurrentBag chấp nhận các bản sao. - The Light
ConcurrentBag là bộ sưu tập không có thứ tự, vì vậy không giống như List<T> nó không đảm bảo đặt hàng. Ngoài ra, bạn không thể truy cập các mục theo chỉ mục. - Radek Stromský
@ RadekStromský là đúng, và trong trường hợp bạn muốn có một danh sách đồng thời đặt hàng, bạn có thể thử ConcurrentQueue (FIFO) hoặc là ConcurrentStack (LIFO). - Caio Cunha
Có lẽ SynchronizedCollection <T> ? - Saturn Technologies
ConcurrentBag không triển khai thực thể IList và không thực sự là phiên bản an toàn của danh sách - Vasyl Zvarydchuk


Ngay cả khi nó có nhiều phiếu bầu nhất, người ta thường không thể System.Collections.Concurrent.ConcurrentBag<T> như một sự thay thế an toàn chỉ cho System.Collections.Generic.List<T> như nó là (Radek Stromský đã chỉ ra nó) không ra lệnh.

Nhưng có một lớp được gọi là System.Collections.Generic.SynchronizedCollection<T> đó là từ .NET 3.0 một phần của framework, nhưng nó ẩn ở một vị trí mà người ta không ngờ rằng nó ít được biết đến và có lẽ bạn chưa bao giờ tình cờ gặp nó (ít nhất là tôi chưa bao giờ làm).

SynchronizedCollection<T> được biên dịch thành lắp ráp System.ServiceModel.dll (đó là một phần của hồ sơ khách hàng nhưng không phải của thư viện lớp di động).

Hy vọng rằng sẽ giúp.


68
2018-05-03 09:15



Gợi ý tốt. Tính di động vẫn quan trọng. - Xaqron
Tôi khóc rằng đây không phải là trong lib cốt lõi: {Một bộ sưu tập đồng bộ đơn giản thường là tất cả những gì cần thiết. - user2864740
Thảo luận hữu ích bổ sung về tùy chọn này: stackoverflow.com/a/4655236/12484 - Jon Schneider
Nó được ẩn đi vì không được chấp nhận, ủng hộ các lớp trong System.Collections.Concurrent. - denfromufa
Và không có trong lõi .net - denfromufa


Tôi nghĩ rằng việc tạo một lớp ThreadSafeList mẫu sẽ dễ dàng:

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;
    }
}

Bạn chỉ cần sao chép danh sách trước khi yêu cầu một điều tra viên, và do đó bất kỳ điều tra nào đang làm việc trên một bản sao mà không thể sửa đổi trong khi chạy.


15
2018-05-03 19:15



Đây không phải là bản sao nông sao? Nếu T là một kiểu tham chiếu, điều này không chỉ trả về một danh sách mới chứa các tham chiếu đến tất cả các đối tượng gốc? Nếu đúng như vậy, cách tiếp cận này vẫn có thể gây ra sự cố luồng vì các đối tượng danh sách có thể được truy cập bởi nhiều luồng thông qua các "bản sao" khác nhau của danh sách. - Joel B
Đúng, nó là một bản sao nông. Vấn đề là chỉ cần có một tập hợp nhân bản sẽ an toàn để lặp lại (vì vậy newListkhông có bất kỳ mục nào được thêm hoặc xóa sẽ làm mất hiệu lực điều tra viên). - Tejs
_Lock có nên tĩnh không? - Mike Ward
Một ý nghĩ khác. Đây có phải là luồng thực hiện an toàn cho nhiều nhà văn không? Nếu không, có lẽ nó nên được gọi là ReadSafeList. - Mike Ward
@MikeWard - Tôi không nghĩ là như vậy, tất cả các Ví dụ sẽ khóa khi bất kì Ví dụ đang được nhân bản! - Josh M.


Ngay cả chấp nhận câu trả lời là ConcurrentBag, tôi không nghĩ rằng nó thực sự thay thế danh sách trong mọi trường hợp, như bình luận của Radek cho câu trả lời: "ConcurrentBag là bộ sưu tập không có thứ tự, vì vậy không giống như Danh sách nó không đảm bảo đặt hàng. Ngoài ra bạn không thể truy cập các mục theo chỉ mục ".

Vì vậy, nếu bạn sử dụng .NET 4.0 trở lên, có thể sử dụng giải pháp thay thế ConcurrentDictionary với số nguyên TKey làm chỉ mục mảng và Giá trị truyền hình là giá trị mảng. Đây là cách được đề nghị thay thế danh sách trong Pluralsight's C # Bộ sưu tập đồng thời khóa học. ConcurrentDictionary giải quyết cả hai vấn đề được đề cập ở trên: chỉ mục truy cập và đặt hàng (chúng tôi không thể dựa vào thứ tự vì nó là bảng băm dưới mui xe, nhưng hiện tại. NET thực hiện tiết kiệm thứ tự của các yếu tố thêm).


4
2018-03-31 18:49



xin vui lòng cung cấp lý do cho -1 - tytyryty
Tôi đã không bỏ phiếu và không có lý do gì cho IMO. Bạn đúng nhưng khái niệm đã được đề cập trong một số câu trả lời. Với tôi, vấn đề là có một bộ sưu tập mới an toàn trong .NET 4.0 mà tôi không biết. Không chắc chắn đã sử dụng Túi hoặc Bộ sưu tập cho tình huống này. +1 - Xaqron
cảm ơn bạn Xaqron! - tytyryty
Câu trả lời này có một số vấn đề: 1) ConcurrentDictionary là một từ điển, không phải là một danh sách. 2) Nó không được bảo đảm để bảo quản trật tự, như tiểu bang trả lời của riêng bạn, mà mâu thuẫn với lý do đã nêu của bạn để đăng một câu trả lời. 3) Nó liên kết với một video mà không đưa các trích dẫn có liên quan vào câu trả lời này (có thể không phù hợp với giấy phép của họ). - jpmc26


Bạn có thể dùng:

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

thế nào để tạo chủ đề an toàn ArrayLsit


2
2017-10-08 18:24



Bạn đang nói tiếng gì vậy? - John Demetriou
Java? Một trong số ít tính năng mà tôi bỏ lỡ. Nhưng nó thường được viết là: Collections.synchronizedList (new ArrayList ()); - Nick


Bạn cũng có thể sử dụng nguyên thủy hơn

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

sử dụng khóa nào (xem bài đăng này C # Khóa một đối tượng được gán lại trong khối khóa).

Nếu bạn đang mong đợi các ngoại lệ trong mã, điều này không an toàn nhưng nó cho phép bạn thực hiện một số việc như sau:

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");
        }
    }
}

Một trong những điều tốt đẹp về điều này là bạn sẽ nhận được khóa trong suốt thời gian của loạt các hoạt động (thay vì khóa trong mỗi hoạt động). Điều đó có nghĩa là đầu ra sẽ xuất hiện ở phần bên phải (việc sử dụng của tôi về điều này đã nhận được một số đầu ra trên màn hình từ một quá trình bên ngoài)

Tôi thực sự thích sự đơn giản + minh bạch của ThreadSafeList +, điều đó có ý nghĩa quan trọng trong việc dừng các sự cố


1
2017-08-22 09:43





Nếu bạn nhìn vào mã nguồn cho Danh sách T (https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877) bạn sẽ nhận thấy có một lớp ở đó (tất nhiên là nội bộ - tại sao, Microsoft, tại sao?!?!) được gọi là SynchronizedList của T. Tôi đang sao chép dán mã ở đây:

   [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);
            }
        }
    }

Cá nhân tôi nghĩ rằng họ biết một triển khai tốt hơn bằng cách sử dụng SemaphoreSlim có thể được tạo ra, nhưng không nhận được nó.


1
2017-08-12 19:46



+1 Khóa toàn bộ bộ sưu tập (_root) trong mỗi truy cập (đọc / ghi) làm cho một giải pháp chậm. Có lẽ tốt hơn là lớp này vẫn còn ở bên trong. - Xaqron


tôi tin _list.ToList() sẽ làm cho bạn một bản sao. Bạn cũng có thể truy vấn nó nếu bạn cần như:

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

Dù sao, msdn nói đây thực sự là một bản sao và không chỉ đơn giản là một tài liệu tham khảo. Oh, và có, bạn sẽ cần phải khóa trong phương pháp thiết lập như những người khác đã chỉ ra.


0
2018-05-03 19:12





Đây là lớp bạn yêu cầu:

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 );
                    }
                }
            }
        }
    }
}

-1
2017-12-22 22:36



đăng mã thực - Cole Johnson
Phiên bản trên Google Drive được cập nhật khi tôi cập nhật lớp học. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html - Protiguous
Tại sao this.GetEnumerator(); khi @Tejs gợi ý this.Clone().GetEnumerator();? - Cœur
Tại sao [DataContract( IsReference = true )]? - Cœur
Phiên bản mới nhất hiện đã có trên GitHub! github.com/AIBrain/Libietnam/blob/master/Collections/… - Protiguous


Về cơ bản nếu bạn muốn liệt kê một cách an toàn, bạn cần phải sử dụng khóa.

Xin vui lòng tham khảo MSDN về điều này. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

Đây là một phần của MSDN mà bạn có thể quan tâm:

Các thành viên tĩnh công cộng (Được chia sẻ trong Visual Basic) thuộc loại này là luồng an toàn. Bất kỳ thành viên cá thể nào cũng không được bảo đảm là luồng an toàn.

Danh sách có thể hỗ trợ đồng thời nhiều độc giả, miễn là bộ sưu tập không được sửa đổi. Việc đếm thông qua một bộ sưu tập thực chất không phải là một thủ tục an toàn chỉ. Trong trường hợp hiếm hoi mà một liệt kê có liên quan đến một hoặc nhiều lần truy cập viết, cách duy nhất để đảm bảo an toàn luồng là khóa bộ sưu tập trong toàn bộ liệt kê. Để cho phép bộ sưu tập được truy cập bởi nhiều luồng để đọc và viết, bạn phải thực hiện đồng bộ hóa của riêng bạn.


-3
2018-05-03 19:06



Không đúng chút nào. Bạn có thể sử dụng các tập hợp đồng thời. - ANeves