İş Parçacığı İçin Güvenli Liste <T> özelliği


123

Hiç List<T>şüphesiz iş parçacığı güvenli bir şekilde kullanılabilecek bir özellik olarak uygulanmasını istiyorum .

Bunun gibi bir şey:

private List<T> _list;

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

Görünüşe göre hala koleksiyonun bir kopyasını (klonlanmış) iade etmem gerekiyor, böylece bir yerde koleksiyonu yineliyorsak ve aynı zamanda koleksiyon ayarlandıysa, herhangi bir istisna ortaya çıkmaz.

İş parçacığı açısından güvenli bir koleksiyon özelliği nasıl uygulanır?


4
kilitler kullan, bunu yapmalı.
atoMerz

IList<T>(Vs List<T>) ' nin iş parçacığı açısından güvenli bir uygulamasını kullanabilir mi?
Greg


BlockingCollection veya ConcurrentDictionary kullanın
kumar chandraketu

Mülkün arkasındaki nesne ile hangi işlemleri yapmanız gerekiyor? Uygulayan her şeye ihtiyacınız olmaması mümkün mü List<T>? Cevabınız evet ise, List<T>zaten sahip olduğunuz her şeyi sormak yerine ihtiyacınız olan bir arayüz sağlayabilir misiniz ?
Victor Yarema

Yanıtlar:


186

.Net 4'ü hedefliyorsanız, System.Collections.Concurrent Namespace'de birkaç seçenek vardır.

ConcurrentBag<T>Bu durumda yerine kullanabilirsinizList<T>


5
List <T> gibi ve Dictionary'den farklı olarak ConcurrentBag kopyaları kabul eder.
The Light

115
ConcurrentBagsırasız koleksiyondur, bu nedenle List<T>siparişi garanti etmez. Ayrıca öğelere indekse göre erişemezsiniz.
Radek Stromský

11
@ RadekStromský haklı ve sıralı bir eşzamanlı liste istemeniz durumunda ConcurrentQueue (FIFO) veya ConcurrentStack (LIFO) deneyebilirsiniz .
Caio Cunha


12
ConcurrentBag
IList'i

87

En çok oyu alsa bile, sipariş edilmemiş olduğu için (Radek Stromský zaten işaret etti) genellikle System.Collections.Concurrent.ConcurrentBag<T>iş parçacığı güvenli bir yedek System.Collections.Generic.List<T>olarak kabul edilemez.

Ancak, System.Collections.Generic.SynchronizedCollection<T>.NET 3.0'dan beri çerçevenin bir parçası olarak adlandırılan bir sınıf var , ancak çok az bilindiğini ve muhtemelen hiç tökezlemediğinizi beklemediğiniz bir yerde o kadar iyi gizlenmiş ki (en azından Asla yapmadım).

SynchronizedCollection<T>derleme System.ServiceModel.dll'de derlenir (istemci profilinin bir parçasıdır, ancak taşınabilir sınıf kitaplığının bir parçası değildir).

Umarım yardımcı olur.


3
Bunun core lib'de olmadığı için ağlıyorum: {Basit bir senkronize koleksiyon genellikle gerekli olan tek şeydir.
user2864740

Bu seçenekle ilgili ek yararlı tartışma: stackoverflow.com/a/4655236/12484
Jon Schneider

2
System.Collections.Concurrent içindeki sınıfların lehine, kullanımdan kaldırıldığı için iyi gizlenmiştir.
denfromufa

3
Ve .net çekirdekte mevcut değil
denfromufa

2
@denfromufa, bunu .net core 2.0'a eklediler gibi görünüyor. docs.microsoft.com/en-gb/dotnet/api/…
Cirelli94

17

Örnek bir ThreadSafeList sınıfı oluşturmanın kolay olacağını düşünürdüm:

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

Bir numaralandırıcı istemeden önce listeyi basitçe klonlarsınız ve böylece herhangi bir numaralandırma, çalışırken değiştirilemeyen bir kopya üzerinde çalışır.


1
Bu sığ bir klon değil mi? Eğer Tbir referans türüdür bu sadece tüm orijinal nesneler referanslar içeren yeni bir liste döndürmez? Durum böyleyse, liste nesnelerine listenin farklı "kopyaları" üzerinden birden çok iş parçacığı tarafından erişilebildiğinden, bu yaklaşım hala iş parçacığı sorununa neden olabilir.
Joel B

3
Doğru, sığ bir kopya. Önemli olan, üzerinde yinelemenin güvenli olacağı klonlanmış bir kümeye sahip olmaktı (bu nedenle newList, numaralandırıcıyı geçersiz kılacak herhangi bir öğe eklenmiş veya kaldırılmış değildir).
Tejs

7
_Lock statik olmalı mı?
Mike Ward

4
Başka bir düşünce. Bu uygulama, birden çok yazar için güvenli midir? Değilse, belki bir ReadSafeList olarak adlandırılmalıdır.
Mike Ward

5
@MikeWard - Olması gerektiğini düşünmüyorum, herhangi bir örnek klonlandığında tüm örnek kilitlenecek !
Josh M.

11

Kabul edilen cevap ConcurrentBag olsa bile, her durumda listenin gerçek yerini aldığını sanmıyorum, çünkü Radek'in cevaba yaptığı yorum: "ConcurrentBag sıralanmamış koleksiyondur, bu yüzden List'in aksine sıralamayı garanti etmez. Ayrıca indekse göre öğelere erişemezsiniz. ".

Dolayısıyla, .NET 4.0 veya üstünü kullanırsanız, ConcurrentDictionary ile dizi dizini olarak TKey ve dizi değeri olarak TValue tamsayısını kullanmak olabilir . Bu, Pluralsight'ın C # Eşzamanlı Koleksiyonlar kursundaki listeyi değiştirmenin önerilen yoludur . ConcurrentDictionary yukarıda bahsedilen her iki sorunu da çözer: indeks erişimi ve sıralaması (başlık altında hash tablosu olduğu için siparişe güvenemeyiz, ancak mevcut .NET uygulaması eleman ekleme sırasını kaydeder).


1
lütfen -1
tytyryty

Olumsuz oy vermedim ve bunun için bir neden yok IMO. Haklısın ama kavram bazı cevaplarda zaten belirtilmiş. Benim için asıl mesele, .NET 4.0'da farkında olmadığım yeni bir iş parçacığı güvenli koleksiyon olmasıydı. Durum için Çanta veya Koleksiyon kullanmadığından emin değilim. +1
Xaqron

2
Bu cevabın birkaç sorunu var: 1) ConcurrentDictionaryliste değil, sözlüktür. 2) Kendi cevabınızın belirttiği gibi, bir cevap göndermek için belirttiğiniz gerekçeyle çelişen, siparişi muhafaza etme garantisi yoktur. 3) İlgili alıntıları bu cevaba getirmeden bir videoya bağlanır (ki bu da lisanslarına uygun olmayabilir).
jpmc26

current implementationBelgelendirme tarafından açıkça garanti edilmediği gibi şeylere güvenemezsiniz . Uygulama herhangi bir bildirimde bulunulmaksızın değiştirilebilir.
Victor Yarema

@ jpmc26, evet elbette List için tam bir ikame değildir, ancak aynı şey kabul edilen bir cevap olarak ConcurrentBag ile de geçerlidir - List'in tam olarak değiştirilmesi değildir, ancak geçici bir çözümdür. Endişelerinizi yanıtlamak için: 1) ConcurrentDictionary, haklı olduğunuz bir liste değil, bir sözlüktür, ancak listenin arkasında bir dizi vardır, o (1) 'de int anahtarıyla int ile aynı şekilde indeksleyebilir 2) yes sıralaması doc tarafından garanti edilmez ( korunmuş olsa bile), ancak kabul edildi ConcurrentBag, çok iş parçacıklı senaryolarda da siparişi garanti edemez
tytyryty

9

C # 'ın ArrayListsınıfının bir Synchronizedyöntemi var.

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

Bu, herhangi bir örneğinin etrafında iş parçacığı güvenli bir sarmalayıcı döndürür IList. İplik güvenliğini sağlamak için tüm işlemlerin sarıcı aracılığıyla gerçekleştirilmesi gerekir.


1
Hangi dilden bahsediyorsun
John Demetriou

Java? Özlediğim birkaç özellikten biri. Ancak genellikle şu şekilde yazılır: Collections.synchronizedList (new ArrayList ());
Nick

2
Bu, System.Collections kullanan bir sisteminiz olduğunu varsayarsak veya var System.Collections.ArrayList.Synchronized (new System.Collections.ArrayList ()) kullanabilirsiniz;
user2163234

5

List of T ( https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 ) için kaynak koduna bakarsanız, orada bir sınıf olduğunu fark edeceksiniz (tabii ki dahili - neden, Microsoft, neden?!?!) SynchronizedList of T olarak adlandırılır. Kodu buraya kopyalayıp yapıştırıyorum:

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

Şahsen , SemaphoreSlim kullanılarak daha iyi bir uygulamanın oluşturulabileceğini bildiklerini ama ulaşamadıklarını düşünüyorum.


2
+1 _rootHer erişimde (okuma / yazma) tüm koleksiyonu ( ) kilitlemek, bunu yavaş bir çözüm haline getirir. Belki bu sınıfın içsel kalması daha iyidir.
Xaqron

3
Bu uygulama iş parçacığı açısından güvenli değildir. Hala "System.InvalidOperationException: 'Koleksiyon değiştirildi; numaralandırma işlemi yürütülemeyebilir." "
Raman Zhylich

2
Bu, iş parçacığı güvenliğiyle değil, koleksiyonu yinelediğiniz ve değiştirdiğiniz gerçeğiyle ilgilidir. İstisna, numaralandırıcı tarafından listenin değiştirildiğini gördüğünde atılır. Bunu aşmak için kendi IEnumerator'ınızı uygulamanız veya kodu aynı anda yinelemeyecek ve değiştirmeyecek şekilde değiştirmeniz gerekir.
Siderite Zackwehdex

Bu evreli değil koleksiyon çünkü olabilir "senkronize" yöntemlerle sırasında değiştirilemez. Bu kesinlikle bir iş parçacığı güvenliği parçası. Bir iş parçacığı aramasını Clear()diğer aramalardan sonra this[index]ancak kilit etkinleştirilmeden önce düşünün . indexartık güvenli değildir ve nihayet çalıştırıldığında bir istisna atar.
Suncat2000

2

Daha ilkel olanı da kullanabilirsiniz.

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

hangi kilidi kullanır (bu C # yazısına bakın Kilit bloğunda yeniden atanmış bir nesneyi kilitleme ).

Kodda istisnalar bekliyorsanız, bu güvenli değildir, ancak aşağıdaki gibi bir şey yapmanıza izin verir:

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

Bununla ilgili güzel şeylerden biri, bir dizi işlemin süresi boyunca kilit alacaksınız (her işlemde kilitlemek yerine). Bu, çıktının doğru parçalar halinde çıkması gerektiği anlamına gelir (bunu kullanımım, harici bir işlemden ekrana bir miktar çıktı almaktı)

Çökmeleri durdurmada önemli olan ThreadSafeList + 'in sadeliğini + şeffaflığını gerçekten seviyorum



1

Sana _list.ToList()bir kopya yapacağına inanıyorum . Aşağıdakilere ihtiyaç duyarsanız da sorgulayabilirsiniz:

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

Her neyse, msdn bunun gerçekten bir kopya olduğunu ve sadece bir referans olmadığını söylüyor. Ve evet, diğerlerinin de belirttiği gibi set yöntemini kilitlemeniz gerekecek.


1

Görünüşe göre bunu bulan pek çok kişi iş parçacığı güvenli dizine alınmış dinamik boyutlu bir koleksiyon istiyor. Bildiğim en yakın ve en kolay şey olurdu.

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

Bu, normal indeksleme davranışı istiyorsanız, anahtarınızın uygun şekilde suçlanmasını sağlamanızı gerektirir. Dikkatli olursanız, eklediğiniz herhangi bir yeni anahtar-değer çifti için anahtar olarak .count yeterli olabilir.


1
Anahtarın hatası olmadığı halde neden anahtar suçlansın?
Suncat2000

@ Suncat2000 ha!
Richard II

1

List<T>Çok iş parçacıklı senaryolarla uğraşan herkese , Immutable Collections , özellikle de ImmutableArray'e bakmalarını öneririm .

Şunlara sahip olduğunuzda çok yararlı buldum:

  1. Listedeki nispeten az öğe
  2. Çok fazla okuma / yazma işlemi yok
  3. Çok sayıda eşzamanlı erişim (yani listeye okuma modunda erişen birçok iş parçacığı)

Ayrıca, bir tür işlem benzeri davranış uygulamanız gerektiğinde de yararlı olabilir (yani, başarısızlık durumunda bir ekleme / güncelleme / silme işlemini geri alma)


-1

İşte istediğin ders:

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 Drive'daki sürüm, sınıfı güncelledikçe güncelleniyor. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html
Protiguous

Neden this.GetEnumerator();@Tejs önerdiğinde this.Clone().GetEnumerator();?
Cœur

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


Add () yöntemlerinde iki küçük hata buldum ve düzelttim. Bilginize.
Protiguous

-3

Temel olarak, güvenli bir şekilde numaralandırmak istiyorsanız, kilidi kullanmanız gerekir.

Lütfen bununla ilgili MSDN'ye bakın. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

İşte ilginizi çekebilecek MSDN bir parçası:

Bu türün genel statik (Visual Basic'te Paylaşılan) üyeleri iş parçacığı açısından güvenlidir. Herhangi bir örnek üyesinin iş parçacığı açısından güvenli olduğu garanti edilmez.

Bir Liste, koleksiyon değiştirilmediği sürece aynı anda birden çok okuyucuyu destekleyebilir. Bir koleksiyon aracılığıyla numaralandırma, özünde iş parçacığı açısından güvenli bir yordam değildir. Bir numaralandırmanın bir veya daha fazla yazma erişimiyle uğraştığı ender durumlarda, iş parçacığı güvenliğini sağlamanın tek yolu, tüm numaralandırma sırasında koleksiyonu kilitlemektir. Koleksiyona okuma ve yazma için birden çok iş parçacığı tarafından erişilmesine izin vermek için, kendi eşitlemenizi uygulamanız gerekir.


2
Hiç doğru değil. Eşzamanlı kümeleri kullanabilirsiniz.
ANeves

-3

Kilitsiz iş parçacığı güvenli listesinin sınıfı

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

Bu konu güvenli değil
Aldracor

_i ++, iş parçacığı güvenli değildir. Arttırdığınızda atomik bir eklenti kullanmanız ve muhtemelen onu da uçucu olarak işaretlemeniz gerekir. CheckReset () iş parçacığı güvenli değil. Koşullu kontrol ile Reset () çağrısı arasında her şey olabilir. Kendi çoklu okuma araçlarınızı yazmayın.
Chris Rollins

-15

lockBunu yapmak için ifadeyi kullanın . ( Daha fazla bilgi için burayı okuyun. )

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

Bilginize, bu muhtemelen tam olarak istediğiniz şey değildir - muhtemelen kodunuzu daha fazla kilitlemek istersiniz, ancak bunu varsayamıyorum. lockAnahtar kelimeye bir göz atın ve kullanımını özel durumunuza göre uyarlayın.

Eğer gerekiyorsa, yapabilirsin lockhem getve setkullanma bloğu _listbir okuma öyle yapacak değişkeni / yazma aynı anda gerçekleşemez.


1
Bu onun problemini çözmeyecek; yalnızca iş parçacıklarının referansı ayarlamasını durdurur, listeye ekleme yapmaz.
Tejs

Ya bir iş parçacığı değeri ayarlıyorsa, diğeri koleksiyonu yineliyorsa (kodunuzla mümkündür).
Xaqron

Dediğim gibi, kilidin muhtemelen kodda daha ileri taşınması gerekecek. Bu sadece lock ifadesinin nasıl kullanılacağına dair bir örnektir.
Josh M.

2
@Joel Mueller: Elbette, bunun gibi aptalca bir örnek üretiyorsanız. Ben sadece soruyu soranın lockifadeye bakması gerektiğini göstermeye çalışıyorum . Benzer bir örnek kullanarak, uygulamayı neredeyse hiç çaba harcamadan kilitleyebileceğiniz için döngüler için kullanmamamız gerektiğini söyleyebilirim:for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Josh M.

5
Kodunuzun anında kilitlenme anlamına geldiğini asla iddia etmedim. Aşağıdaki nedenlerden dolayı bu soruya kötü bir cevap: 1) Listenin numaralandırılması sırasında veya aynı anda iki iş parçacığı tarafından değiştirilen listenin içeriğine karşı koruma sağlamaz. 2) Ayarlayıcıyı kilitlemek, ancak alıcıyı kilitlemek, özelliğin gerçekten iş parçacığı açısından güvenli olmadığı anlamına gelir. 3) Sınıf dışından erişilebilen herhangi bir referansa kilitlenmek , yaygın olarak kötü bir uygulama olarak kabul edilir, çünkü bu, kazara kilitlenme olasılığını önemli ölçüde artırır. En yüzden lock (this)ve lock (typeof(this))büyük hayır-hayır kullanıcısının vardır.
Joel Mueller
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.