Thuộc tính Danh sách <T> an toàn theo chuỗi


122

Tôi muốn triển khai List<T>như một thuộc tính có thể được sử dụng theo luồng một cách an toàn mà không nghi ngờ gì.

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 để triển khai thuộc tính thu thập an toàn theo luồng?


4
sử dụng khóa, điều đó nên làm điều đó.
atoMerz

Có thể sử dụng sử dụng triển khai an toàn luồng của IList<T>(vs List<T>) không?
Greg

2
Bạn đã kiểm tra SynchronizedCollection <T> chưa?
Saturn Technologies

Sử dụng BlockingCollection hoặc ConcurrentDictionary
kumar chandraketu

Những thao tác nào bạn cần làm với đối tượng phía sau thuộc tính? Có thể là bạn không cần tất cả mọi thứ mà List<T>thực hiện? Nếu có, thì bạn có thể vui lòng cung cấp một giao diện mà bạn cần thay vì hỏi về mọi thứ List<T>đã có không?
Victor Yarema

Câu trả lời:


185

Nếu bạn đang nhắm mục tiêu .Net 4, có một số tùy chọn trong System.Collections.Concurrent Namespace

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


5
Giống như List <T> và không giống như Dictionary, ConcurrentBag chấp nhận các bản sao.
The Light

115
ConcurrentBaglà 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 thứ tự. Ngoài ra, bạn không thể truy cập các mục theo chỉ mục.
Radek Stromský

11
@ RadekStromský nói đúng, và trong trường hợp bạn muốn một danh sách đồng thời có thứ tự, bạn có thể thử ConcurrentQueue (FIFO) hoặc ConcurrentStack (LIFO) .
Caio Cunha


12
ConcurrentBag không thực hiện IList và không thực sự là thread phiên bản an toàn Danh sách
Vasyl Zvarydchuk

87

Ngay cả khi nó nhận được nhiều phiếu bầu nhất, nó thường không thể được coi System.Collections.Concurrent.ConcurrentBag<T>là sự thay thế an toàn cho luồng System.Collections.Generic.List<T>vì nó vốn có (Radek Stromský đã chỉ ra nó) không được đặt hàng.

Nhưng có một lớp được gọi System.Collections.Generic.SynchronizedCollection<T>là đã có từ khi .NET 3.0 là một phần của khung công tác, nhưng nó ẩn rất nhiều ở 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ờ tìm thấy nó (ít nhất là Tôi không bao giờ làm).

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

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


3
Tôi rất tiếc rằng điều này không có trong core lib: {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

2
Nó bị ẩn đi vì không được dùng nữa, có lợi cho các lớp trong System.Collections.Concurrent.
denfromufa

3
Và không có sẵn trong .net lõi
denfromufa

2
@denfromufa có vẻ như họ đã thêm cái này vào .net core 2.0 docs.microsoft.com/en-gb/dotnet/api/…
Cirelli94

17

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ỳ liệt kê nào cũng đang xử lý một bản sao không thể sửa đổi trong khi chạy.


1
Đây không phải là một phân thân nông cạn sao? Nếu Tlà một loại tham chiếu, điều này sẽ 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 ban đầu? Nếu đúng như vậy, cách tiếp cận này vẫn có thể gây ra sự cố phân luồng vì các đối tượng danh sách có thể được nhiều luồng truy cập thông qua các "bản sao" khác nhau của danh sách.
Joel B

3
Đúng, đó là một bản sao nông cạn. Vấn đề là chỉ cần có một tập hợp được 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 vào hoặc loại bỏ sẽ làm mất hiệu lực của điều tra viên).
Tejs

7
_Lock có nên tĩnh không?
Mike Ward

4
Một suy nghĩ khác. Việc triển khai này có an toàn cho nhiều người viết không? Nếu không, có lẽ nó nên được gọi là ReadSafeList.
Mike Ward

5
@MikeWard - Tôi không nghĩ phải như vậy, tất cả phiên bản sẽ khóa khi bất kỳ phiên bản nào được nhân bản!
Josh M.

11

Ngay cả câu trả lời được chấp nhận 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ư nhận xét của Radek cho câu trả lời cho biết: "ConcurrentBag là bộ sưu tập không có thứ tự, vì vậy không giống như List, nó không đảm bảo thứ tự. 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, giải pháp thay thế có thể là sử dụng ConcurrentDictionary với TKey số nguyên làm chỉ số mảng và TValue làm giá trị mảng. Đây là cách được đề xuất để thay thế danh sách trong khóa học Bộ sưu tập đồng thời C # của Pluralsight . ConcurrentDictionary giải quyết được cả hai vấn đề được đề cập ở trên: truy cập chỉ mục và sắp xếp thứ tự (chúng ta không thể dựa vào thứ tự vì đó là bảng băm, nhưng việc triển khai .NET hiện tại sẽ tiết kiệm thứ tự của các phần tử được thêm vào).


1
vui lòng cung cấp lý do -1
tytyryty

Tôi đã không bỏ phiếu và không có lý do gì cho việc đó IMO. Bạn đúng nhưng khái niệm này đã được đề cập trong một số câu trả lời. Đối với tôi, vấn đề là có một bộ sưu tập an toàn luồng mới 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. +1
Xaqron

2
Câu trả lời này có một số vấn đề: 1) ConcurrentDictionarylà một từ điển, không phải là một danh sách. 2) Nó không được đảm bảo để duy trì thứ tự, như câu trả lời của riêng bạn đã nêu, điều này mâu thuẫn với lý do bạn đã nêu khi đăng câu trả lời. 3) Nó liên kết đến 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 (dù sao điều này có thể không phù hợp với việc cấp phép của họ).
jpmc 26

Bạn không thể dựa vào những thứ như current implementationnếu nó không được tài liệu đảm bảo rõ ràng. Việc thực hiện có thể thay đổi bất kỳ lúc nào mà không cần thông báo trước.
Victor Yarema

@ jpmc26, vâng, nó không phải là sự thay thế hoàn toàn cho Danh sách, tuy nhiên điều tương tự với ConcurrentBag như một câu trả lời được chấp nhận - nó không phải là sự thay thế nghiêm ngặt của Danh sách mà là giải pháp. Để giải đáp thắc mắc của bạn: 1) ConcurrentDictionary là một từ điển không phải là một danh sách mà bạn nói đúng, tuy nhiên, danh sách có mảng phía sau, có thể lập chỉ mục trong O (1) giống như từ điển với int làm khóa 2) có thứ tự không được đảm bảo bởi doc ( mặc dù nó được bảo tồn), nhưng chấp nhận ConcurrentBag không thể để đảm bảo cũng như trong các kịch bản đa luồng
tytyryty

9

ArrayListLớp của C # có một Synchronizedphương thức.

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

Điều này trả về một trình bao bọc an toàn luồng xung quanh bất kỳ trường hợp nào của IList. Tất cả các hoạt động cần được thực hiện thông qua trình bao bọc để đảm bảo an toàn cho luồng.


1
Bạn đang nói tiếng gì vậy?
John Demetriou

Java? Một trong những tính năng tôi bỏ lỡ về nó. Nhưng nó thường được viết là: Collections.synchronizedList (new ArrayList ());
Nick

2
Đây là C # hợp lệ giả sử bạn có sử dụng System.Collections hoặc bạn có thể sử dụng var System.Collections.ArrayList.Synchronized (new System.Collections.ArrayList ());
user2163234

5

Nếu bạn nhìn vào mã nguồn của 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 of 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 thực hiện được.


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

3
Việc triển khai này không an toàn theo chuỗi. Nó vẫn ném "System.InvalidOperationException: 'Bộ sưu tập đã được sửa đổi; hoạt động liệt kê có thể không thực thi.'"
Raman Zhylich.

2
Điều đó không liên quan đến an toàn luồng, mà là thực tế là bạn đang lặp lại và thay đổi bộ sưu tập. Điều tra viên ném ngoại lệ khi thấy danh sách đã bị thay đổi. Để giải quyết vấn đề này, bạn cần triển khai IEnumerator của riêng mình hoặc thay đổi mã để nó không lặp lại và thay đổi cùng một bộ sưu tập cùng một lúc.
Siderite Zackwehdex,

Nó không an toàn theo chuỗi vì bộ sưu tập có thể được thay đổi trong các phương thức "đồng bộ hóa". Đó hoàn toàn một phần của an toàn luồng. Xem xét một cuộc gọi luồng Clear()sau cuộc gọi khác this[index]nhưng trước khi khóa được kích hoạt. indexkhông còn an toàn để sử dụng và sẽ ném một ngoại lệ khi nó cuối cùng được thực thi.
Suncat2000

2

Bạn cũng có thể sử dụng

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

khóa nào sử dụng (xem bài đăng này C # Khóa một đối tượng được chỉ định 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 thú vị về điều này là bạn sẽ nhận được khóa trong suốt thời gian của chuỗi hoạt động (thay vì khóa trong mỗi hoạt động). Có nghĩa là đầu ra sẽ xuất hiện đúng phần (cách sử dụng của tôi về điều này là đưa một số đầu ra lên màn hình từ một quy trình bên ngoài)

Tôi thực sự thích sự đơn giản + minh bạch của ThreadSafeList + có vai trò quan trọng trong việc ngăn chặn sự cố


2

Trong .NET Core (bất kỳ phiên bản nào), bạn có thể sử dụng ImmutableList , có tất cả các chức năng của List<T>.


1

Tôi tin rằng _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, chẳng hạn như:

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

Dù sao, msdn nói rằng đâ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. Ồ, và vâng, bạn sẽ cần phải khóa phương thức thiết lập như những người khác đã chỉ ra.


1

Có vẻ như nhiều người tìm thấy điều này đang muốn một bộ sưu tập có kích thước động được lập chỉ mục an toàn theo chuỗi. Điều gần nhất và dễ dàng nhất mà tôi biết sẽ là.

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

Điều này sẽ yêu cầu bạn đảm bảo khóa của bạn được ghi đúng cách nếu bạn muốn hành vi lập chỉ mục bình thường. Nếu bạn cẩn thận, .count có thể đủ làm khóa cho bất kỳ cặp giá trị khóa mới nào bạn thêm vào.


1
Tại sao phải xử lý khóa khi nó không phải là lỗi của khóa?
Suncat2000

@ Suncat2000 ha!
Richard II,

1

Tôi sẽ đề nghị bất kỳ ai đang xử lý List<T>các kịch bản đa luồng nên xem xét các Bộ sưu tập bất biến, cụ thể là ImmutableArray .

Tôi thấy nó rất hữu ích khi bạn có:

  1. Tương đối ít mục trong danh sách
  2. Không có quá nhiều thao tác đọc / ghi
  3. RẤT NHIỀU quyền truy cập đồng thời (tức là nhiều luồng truy cập danh sách ở chế độ đọc)

Cũng có thể hữu ích khi bạn cần thực hiện một số loại hành vi giống như giao dịch (tức là hoàn nguyên thao tác chèn / cập nhật / xóa trong trường hợp không thành công)


-1

Đây là lớp học mà 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 );
                    }
                }
            }
        }
    }
}

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/Librainian/blob/master/Collections/…
Protiguous

Tôi đã tìm thấy và sửa hai lỗi nhỏ trong phương thức Add (). FYI.
Protiguous

-3

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.

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) của loại này là an toàn luồng. Bất kỳ thành viên cá thể nào đều không được đảm bảo an toàn cho chuỗi.

Một Danh sách có thể hỗ trợ đồng thời nhiều người đọc, miễn là bộ sưu tập không bị sửa đổi. Việc liệt kê thông qua một bộ sưu tập về bản chất không phải là một thủ tục an toàn theo chuỗi. Trong trường hợp hiếm hoi khi một phép liệt kê có một hoặc nhiều quyền truy cập ghi, 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ộ phép liệt kê. Để cho phép bộ sưu tập được nhiều luồng truy cập để đọc và ghi, bạn phải triển khai đồng bộ hóa của riêng mình.


2
Không đúng chút nào. Bạn có thể sử dụng các bộ Đồng thời.
ANeves

-3

Đây là lớp cho danh sách an toàn luồng không có khóa

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

Đây không phải là threadsafe
Aldracor

_i ++ không phải là threadsafe. bạn phải sử dụng thêm nguyên tử khi tăng và có thể đánh dấu nó dễ bay hơi. CheckReset () không phải là chuỗi an toàn. Bất cứ điều gì có thể xảy ra giữa kiểm tra có điều kiện và gọi đến Đặt lại (). Đừng viết các tiện ích đa luồng của riêng bạn.
Chris Rollins

-15

Sử dụng lockcâu lệnh để làm điều này. ( Đọc ở đây để biết thêm thông tin. )

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

FYI đây có thể không phải là chính xác những gì bạn yêu cầu - bạn có thể muốn khóa mã của mình xa hơn nhưng tôi không thể cho rằng điều đó. Hãy xem xét locktừ khóa và điều chỉnh việc sử dụng nó cho phù hợp với tình huống cụ thể của bạn.

Nếu bạn cần, bạn có thể locktrong cả khối getsetbằng cách sử dụng _listbiến để biến nó không thể xảy ra đọc / ghi cùng một lúc.


1
Điều đó sẽ không giải quyết được vấn đề của anh ta; nó chỉ ngăn các chủ đề thiết lập tham chiếu, không thêm vào danh sách.
Tejs

Và điều gì sẽ xảy ra nếu một luồng đang đặt giá trị trong khi luồng khác đang lặp lại bộ sưu tập (có thể với mã của bạn).
Xaqron

Như tôi đã nói, khóa có thể sẽ phải được di chuyển ra xa hơn trong mã. Đây chỉ là một ví dụ về cách sử dụng câu lệnh khóa.
Josh M.

2
@Joel Mueller: Chắc chắn rồi, nếu bạn đưa ra một số ví dụ ngớ ngẩn như vậy. Tôi chỉ đang cố gắng minh họa rằng người hỏi nên xem xét locktuyên bố. Sử dụng một ví dụ tương tự, tôi có thể tranh luận rằng chúng ta không nên sử dụng vòng lặp for vì bạn có thể khóa ứng dụng mà không cần nỗ lực gì:for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Josh M.

5
Tôi chưa bao giờ tuyên bố rằng mã của bạn có nghĩa là bế tắc tức thì. Đó là một câu trả lời tồi cho câu hỏi cụ thể này vì những lý do sau: 1) Nó không bảo vệ khỏi nội dung của danh sách bị sửa đổi trong quá trình liệt kê danh sách hoặc bởi hai chủ đề cùng một lúc. 2) Khóa setter nhưng không khóa getter có nghĩa là thuộc tính không thực sự an toàn cho luồng. 3) Khóa trên bất kỳ tài liệu tham khảo nào có thể truy cập từ bên ngoài lớp được nhiều người coi là một hành vi xấu, vì nó làm tăng đáng kể nguy cơ bế tắc ngẫu nhiên. Đó là lý do tại sao lock (this)lock (typeof(this))là những điều không thể tránh khỏi.
Joel Mueller
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.