Properti Thread-safe List <T>


122

Saya ingin implementasi List<T>sebagai properti yang dapat digunakan dengan aman tanpa keraguan.

Sesuatu seperti ini:

private List<T> _list;

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

Sepertinya saya masih perlu mengembalikan salinan (kloning) dari koleksi jadi jika di suatu tempat kita mengulang koleksi dan pada saat yang sama mengatur koleksi, maka tidak ada pengecualian yang dimunculkan.

Bagaimana cara menerapkan properti koleksi aman thread?


4
gunakan kunci, itu harus dilakukan.
atoMerz

Dapatkah menggunakan implementasi thread-safe dari IList<T>(vs List<T>)?
Greg

2
Sudahkah Anda memeriksa SynchronizedCollection <T> ?
Saturn Technologies

Gunakan BlockingCollection atau ConcurrentDictionary
kumar chandraketu

Operasi apa yang perlu Anda lakukan dengan objek di belakang properti? Mungkinkah Anda tidak membutuhkan semua yang List<T>diimplementasikan? Jika ya, dapatkah Anda memberikan antarmuka yang Anda butuhkan alih-alih bertanya tentang semua yang List<T>sudah dimiliki?
Victor Yarema

Jawaban:


185

Jika Anda menargetkan .Net 4 ada beberapa opsi di System.Collections.Concurrent Namespace

Anda bisa menggunakan ConcurrentBag<T>dalam kasus ini sebagai penggantiList<T>


5
Seperti List <T> dan tidak seperti Dictionary, ConcurrentBag menerima duplikat.
The Light

115
ConcurrentBagadalah koleksi tidak berurutan, jadi tidak seperti List<T>itu tidak menjamin pemesanan. Anda juga tidak dapat mengakses item dengan indeks.
Radek Stromský

11
@ RadekStromský benar, dan jika Anda menginginkan daftar serentak yang dipesan, Anda dapat mencoba ConcurrentQueue (FIFO) atau ConcurrentStack (LIFO) .
Caio Cunha


12
ConcurrentBag tidak mengimplementasikan IList dan sebenarnya bukan versi aman dari Daftar
Vasyl Zvarydchuk

87

Bahkan karena mendapat suara terbanyak, orang biasanya tidak dapat mengambil System.Collections.Concurrent.ConcurrentBag<T>pengganti yang aman untuk threadSystem.Collections.Generic.List<T> apa adanya (Radek Stromský sudah menunjukkannya) tidak dipesan.

Tetapi ada kelas yang disebut System.Collections.Generic.SynchronizedCollection<T>sudah sejak .NET 3.0 bagian dari kerangka kerja, tetapi itu tersembunyi dengan baik di lokasi di mana orang tidak berharap itu sedikit diketahui dan mungkin Anda tidak pernah tersandung di atasnya (setidaknya Saya tidak pernah).

SynchronizedCollection<T>dikompilasi menjadi System.ServiceModel.dll perakitan (yang merupakan bagian dari profil klien tetapi bukan dari perpustakaan kelas portabel).

Semoga membantu.


3
Saya menangis karena ini bukan di lib inti: {Seringkali hanya diperlukan koleksi tersinkronisasi sederhana.
pengguna2864740

Diskusi bermanfaat tambahan dari opsi ini: stackoverflow.com/a/4655236/12484
Jon Schneider

2
Itu tersembunyi dengan baik karena tidak digunakan lagi, mendukung kelas-kelas di System.Collections.Concurrent.
denfromufa

3
Dan tidak tersedia dalam .net core
denfromufa

2
@denfromufa sepertinya mereka menambahkan ini di .net core 2.0 docs.microsoft.com/en-gb/dotnet/api/…
Cirelli94

17

Menurut saya, membuat sampel kelas ThreadSafeList akan mudah:

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

Anda cukup mengkloning daftar sebelum meminta pencacah, dan dengan demikian setiap pencacahan bekerja pada salinan yang tidak dapat diubah saat berjalan.


1
Bukankah ini klon dangkal? Jika Tmerupakan tipe referensi, bukankah ini hanya mengembalikan daftar baru yang berisi referensi ke semua objek asli? Jika demikian, pendekatan ini masih dapat menyebabkan masalah threading karena objek daftar dapat diakses oleh beberapa utas melalui "salinan" daftar yang berbeda.
Joel B

3
Benar, ini salinan yang dangkal. Intinya adalah untuk memiliki satu set kloning yang akan aman untuk mengulang (jadi newListtidak ada item yang ditambahkan atau dihapus yang akan membatalkan pencacah).
Tejs

7
Haruskah _lock menjadi statis?
Mike Ward

4
Pikiran lain. Apakah implementasi ini aman untuk banyak penulis? Jika tidak, mungkin itu harus disebut ReadSafeList.
Mike Ward

5
@MikeWard - Saya rasa tidak seharusnya demikian, semua instance akan terkunci ketika ada instance yang dikloning!
Josh M.

11

Bahkan jawaban yang diterima adalah ConcurrentBag, saya rasa ini bukan pengganti daftar yang sebenarnya dalam semua kasus, karena komentar Radek terhadap jawaban tersebut mengatakan: "ConcurrentBag adalah koleksi tidak berurutan, jadi tidak seperti List, ia tidak menjamin pemesanan. Anda juga tidak dapat mengakses item berdasarkan indeks ".

Jadi, jika Anda menggunakan .NET 4.0 atau lebih tinggi, solusinya dapat menggunakan ConcurrentDictionary dengan integer TKey sebagai indeks array dan TValue sebagai nilai array. Ini adalah cara yang direkomendasikan untuk mengganti daftar di kursus Pluralsight C # Concurrent Collections . ConcurrentDictionary memecahkan kedua masalah yang disebutkan di atas: pengaksesan dan pemesanan indeks (kita tidak dapat mengandalkan pengurutan karena tabel hashnya ada di bawah tenda, tetapi implementasi .NET saat ini menyimpan urutan penambahan elemen).


1
berikan alasan untuk -1
tytyryty

Saya tidak memilih dan tidak ada alasan untuk itu IMO. Anda benar tetapi konsepnya sudah disebutkan dalam beberapa jawaban. Bagi saya, intinya adalah ada koleksi thread-safe baru di .NET 4.0 yang tidak saya sadari. Tidak yakin Tas atau Koleksi bekas untuk situasi tersebut. +1
Xaqron

2
Jawaban ini memiliki beberapa masalah: 1) ConcurrentDictionaryadalah kamus, bukan daftar. 2) Tidak ada jaminan untuk menjaga ketertiban, seperti jawaban Anda sendiri, yang bertentangan dengan alasan Anda untuk memposting jawaban. 3) Ini menautkan ke video tanpa membawa kutipan yang relevan ke dalam jawaban ini (yang mungkin tidak sesuai dengan lisensi mereka).
jpmc26

Anda tidak dapat mengandalkan hal-hal seperti current implementationjika tidak secara eksplisit dijamin oleh dokumentasi. Implementasi dapat berubah sewaktu-waktu tanpa pemberitahuan.
Victor Yarema

@ jpmc26, ya ini bukan pengganti penuh tentu saja untuk Daftar, namun sama dengan ConcurrentBag sebagai jawaban yang diterima - ini bukan pengganti yang ketat dari Daftar tetapi bekerja di sekitar. Untuk menjawab kekhawatiran Anda: 1) ConcurrentDictionary adalah kamus bukan daftar Anda yang benar, namun daftar memiliki larik di belakang, yang dapat mengindeks dalam O (1) sama seperti kamus dengan int sebagai kunci 2) ya urutan tidak dijamin oleh doc ( meskipun disimpan), tetapi ConcurrentBag yang diterima tidak dapat menjamin ketertiban juga dalam skenario multithread
tytyryty

9

ArrayListKelas C # memiliki Synchronizedmetode.

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

Ini mengembalikan pembungkus aman utas di sekitar contoh apa pun IList. Semua operasi perlu dilakukan melalui pembungkusnya untuk memastikan keamanan benang.


1
Bahasa apa yang kamu bicarakan
John Demetriou

Jawa? Salah satu dari sedikit fitur yang saya lewatkan. Tapi biasanya ditulis sebagai: Collections.synchronizedList (new ArrayList ());
Nick

2
Ini adalah C # yang valid dengan asumsi Anda menggunakan System.Collections atau Anda dapat menggunakan var System.Collections.ArrayList.Synchronized (new System.Collections.ArrayList ());
pengguna2163234

5

Jika Anda melihat kode sumber untuk Daftar T ( https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 ) Anda akan melihat ada kelas di sana (yang tentu saja internal - mengapa, Microsoft, mengapa?!?!) disebut SynchronizedList of T. Saya menyalin menempelkan kode di sini:

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

Secara pribadi saya pikir mereka tahu implementasi yang lebih baik menggunakan SemaphoreSlim dapat dibuat, tetapi tidak berhasil.


2
+1 Mengunci seluruh koleksi ( _root) di setiap akses (baca / tulis) menjadikannya solusi yang lambat. Mungkin lebih baik kelas ini tetap internal.
Xaqron

3
Penerapan ini tidak aman untuk thread. Itu masih menampilkan "System.InvalidOperationException: 'Koleksi telah diubah; operasi enumerasi tidak dapat dijalankan.'"
Raman Zhylich

2
Itu tidak terkait dengan keamanan utas, tetapi dengan fakta bahwa Anda mengulang dan mengubah koleksi. Pengecualian dilemparkan oleh pencacah ketika melihat daftar itu diubah. Untuk menyiasati ini, Anda perlu menerapkan IEnumerator Anda sendiri atau mengubah kode sehingga tidak mengulang dan mengubah koleksi yang sama pada waktu yang sama.
Siderite Zackwehdex

Ini tidak aman untuk thread karena koleksi dapat diubah selama metode "disinkronkan". Itu benar-benar adalah bagian dari keselamatan benang. Pertimbangkan satu utas panggilan Clear()setelah panggilan lain this[index]tetapi sebelum kunci diaktifkan. indextidak lagi aman digunakan dan akan menampilkan pengecualian saat akhirnya dijalankan.
Suncat2000

2

Anda juga bisa menggunakan yang lebih primitif

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

kunci mana yang digunakan (lihat posting ini C # Mengunci sebuah objek yang dipindahkan ke blok kunci ).

Jika Anda mengharapkan pengecualian dalam kode ini tidak aman tetapi memungkinkan Anda untuk melakukan sesuatu seperti berikut:

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

Salah satu hal menyenangkan tentang ini adalah Anda akan mendapatkan kunci selama rangkaian operasi (daripada mengunci di setiap operasi). Yang berarti bahwa output harus keluar dalam bagian yang benar (penggunaan saya ini adalah mendapatkan beberapa output ke layar dari proses eksternal)

Saya sangat menyukai kesederhanaan + transparansi ThreadSafeList + yang melakukan bagian penting dalam menghentikan crash



1

Saya yakin _list.ToList()akan membuatkan Anda salinan. Anda juga dapat menanyakannya jika Anda perlu seperti:

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

Bagaimanapun, msdn mengatakan ini memang salinan dan bukan hanya referensi. Oh, dan ya, Anda harus mengunci metode set seperti yang ditunjukkan orang lain.


1

Sepertinya banyak orang yang menemukan ini menginginkan koleksi berukuran dinamis yang terindeks aman untuk utas. Hal terdekat dan termudah yang saya tahu adalah.

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

Ini akan mengharuskan Anda untuk memastikan kunci Anda terlibat dengan benar jika Anda menginginkan perilaku pengindeksan yang normal. Jika Anda berhati-hati, .count bisa cukup sebagai kunci untuk pasangan nilai kunci baru yang Anda tambahkan.


1
Mengapa kunci harus dilibatkan padahal itu bukan kesalahan kunci?
Suncat2000

@ Suncat2000 ha!
Richard II

1

Saya akan menyarankan siapa pun yang berurusan dengan List<T>skenario multi-threading untuk melihat Koleksi Abadi, khususnya ImmutableArray .

Saya merasa sangat berguna jika Anda memiliki:

  1. Relatif sedikit item dalam daftar
  2. Tidak banyak operasi baca / tulis
  3. BANYAK akses bersamaan (yaitu banyak utas yang mengakses daftar dalam mode membaca)

Juga dapat berguna saat Anda perlu menerapkan semacam perilaku seperti transaksi (yaitu mengembalikan operasi penyisipan / pembaruan / penghapusan jika terjadi kegagalan)


-1

Inilah kelas yang Anda minta:

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

Versi di Google Drive diperbarui saat saya memperbarui kelas. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html
Protiguous

Mengapa this.GetEnumerator();saat @Tejs menyarankan this.Clone().GetEnumerator();?
Cœur

Kenapa [DataContract( IsReference = true )]?
Cœur


Saya menemukan dan memperbaiki dua bug kecil dalam metode Add (). FYI.
Protiguous

-3

Pada dasarnya jika Anda ingin menghitung dengan aman, Anda perlu menggunakan kunci.

Silakan merujuk ke MSDN tentang ini. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

Berikut adalah bagian dari MSDN yang mungkin menarik bagi Anda:

Anggota publik statis (Dibagikan dalam Visual Basic) jenis ini adalah thread aman. Setiap anggota instance tidak dijamin aman untuk thread.

Sebuah Daftar dapat mendukung banyak pembaca secara bersamaan, selama koleksinya tidak diubah. Menghitung melalui koleksi pada dasarnya bukanlah prosedur yang aman untuk thread. Dalam kasus yang jarang terjadi di mana enumerasi bersaing dengan satu atau lebih akses tulis, satu-satunya cara untuk memastikan keamanan thread adalah dengan mengunci koleksi selama pencacahan keseluruhan. Agar koleksi dapat diakses oleh beberapa utas untuk membaca dan menulis, Anda harus menerapkan sinkronisasi Anda sendiri.


2
Tidak benar sama sekali. Anda dapat menggunakan set serentak.
ANeves

-3

Berikut adalah kelas untuk daftar aman benang tanpa kunci

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

Ini bukan threadsafe
Aldracor

_i ++ bukan threadsafe. Anda harus menggunakan atomic add ketika Anda menaikkannya dan mungkin menandainya juga volatile. CheckReset () bukan threadsafe. Apa pun bisa terjadi di antara pemeriksaan bersyarat dan panggilan ke Reset (). Jangan menulis utilitas multithreading Anda sendiri.
Chris Rollins

-15

Gunakan lockpernyataan tersebut untuk melakukan ini. ( Baca di sini untuk informasi lebih lanjut. )

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 ini mungkin bukan apa yang Anda minta - Anda mungkin ingin mengunci lebih jauh dalam kode Anda tetapi saya tidak dapat berasumsi itu. Silakan lihat dilock kata kunci dan sesuaikan penggunaannya dengan situasi spesifik Anda.

Jika perlu, Anda dapat lockdi blok getdan setmenggunakan _listvariabel yang akan membuatnya jadi baca / tulis tidak dapat terjadi pada saat yang bersamaan.


1
Itu tidak akan menyelesaikan masalahnya; itu hanya menghentikan utas dari menyetel referensi, tidak menambahkan ke daftar.
Tejs

Dan bagaimana jika satu utas menyetel nilai sementara utas lain mengulang koleksi (mungkin dengan kode Anda).
Xaqron

Seperti saya katakan, kunci mungkin harus dipindahkan lebih jauh dalam kode. Ini hanyalah sebuah contoh bagaimana menggunakan pernyataan kunci.
Josh M.

2
@ Joel Mueller: Tentu, jika Anda membuat beberapa contoh konyol seperti itu. Saya hanya mencoba untuk mengilustrasikan bahwa penanya harus memeriksa lockpernyataan tersebut. Dengan menggunakan contoh serupa, saya dapat berpendapat bahwa kita tidak boleh menggunakan for loop karena Anda dapat membuat aplikasi deadlock dengan susah payah:for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Josh M.

5
Saya tidak pernah mengklaim kode Anda berarti kebuntuan instan. Ini adalah jawaban yang buruk untuk pertanyaan khusus ini karena alasan berikut: 1) Ini tidak melindungi dari isi daftar yang diubah selama pencacahan daftar, atau oleh dua utas sekaligus. 2) Mengunci penyetel tetapi bukan pengambil berarti properti tersebut tidak benar-benar aman untuk thread. 3) Mengunci pada setiap referensi yang dapat diakses dari luar kelas secara luas dianggap sebagai praktik buruk, karena secara dramatis meningkatkan kemungkinan kebuntuan disengaja. Itulah mengapa lock (this)dan lock (typeof(this))sangat tidak boleh.
Joel Mueller
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.