Soru İş parçacığı güvenli listesi özelliği


Uygulanması istiyorum List<T> herhangi bir şüphe olmadan güvenle güvenli bir şekilde kullanılabilen bir özellik olarak.

Böyle bir şey:

private List<T> _list;

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

Hala bir kopyasını (klonlanmış) koleksiyona geri dönmem gerekiyor, bu yüzden eğer bir yerde koleksiyonun yinelemesini yapıyorsak ve aynı zamanda koleksiyon ayarlanmışsa, istisna kaldırılmaz.

Bir iş parçacığı güvenli koleksiyon özelliği nasıl uygulanır?


76
2018-05-03 19:01


Menşei


kilitleri kullan, bunu yapmalı. - atoMerz
İş parçacığı için güvenli bir uygulama kullanabilir. IList<T> (vs List<T>)? - Greg
Kontrol ettin mi SynchronizedCollection <T> ? - Saturn Technologies


Cevaplar:


Eğer hedefliyorsanız. Net 4 birkaç seçenek var System.Collections.Concurrent Ad alanı

Kullanabilirsin ConcurrentBag<T> bu durumda yerine List<T>


131
2018-05-03 19:04



Liste <T> ve Sözlükten farklı olarak ConcurrentBag, kopyaları kabul eder. - The Light
ConcurrentBag sıralanmamış koleksiyon, bu yüzden farklı List<T> sipariş vermeyi garanti etmez. Ayrıca, öğelere endeksle erişemezsiniz. - Radek Stromský
@ RadekStromský haklı, ve sıralı bir eşzamanlı liste istiyorsanız, deneyebilirsiniz ConcurrentQueue (FIFO) veya ConcurrentStack (LIFO). - Caio Cunha
Olabilir SynchronizedCollection <T> ? - Saturn Technologies
ConcurrentBag, IList'i uygulamıyor ve aslında Listenin thread güvenli sürümü değil - Vasyl Zvarydchuk


En çok oyu aldığı gibi, genellikle bir şey alamaz System.Collections.Concurrent.ConcurrentBag<T> için diş güvenli bir yedek olarak System.Collections.Generic.List<T> olduğu gibi (Radek Stromský zaten işaret etti) emretti.

Ama adında bir sınıf var System.Collections.Generic.SynchronizedCollection<T> Bu çerçeve zaten .NET 3.0 bölümünden beri, ama birisinin iyi bilinmediği bir yerde saklı olduğu ve muhtemelen hiç bir zaman üzerinde hiç karşılaşmadığınız (en azından hiç yapmadım).

SynchronizedCollection<T> derleme olarak derlenir System.ServiceModel.dll (bu, 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.


68
2018-05-03 09:15



İyi ipucu. Hala taşınabilirlik çok önemlidir. - Xaqron
Ben bunun çekirdek lib'de olmadığını ağlarım: {Basit bir senkronize edilmiş koleksiyon genellikle ihtiyaç duyulan her şeydir. - user2864740
Bu seçeneğin ek yararlı tartışması: stackoverflow.com/a/4655236/12484 - Jon Schneider
System.Collections.Concurrent'daki sınıfların lehine, kullanımdan kaldırıldığı için iyi gizlenmiştir. - denfromufa
Ve .net çekirdeğinde mevcut değil - denfromufa


Ben bir örnek ThreadSafeList sınıfı yapmak kolay olurdu 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 numaralayıcı talep etmeden önce listeyi kopyalamanız yeterlidir ve bu nedenle herhangi bir numaralandırma çalıştırılırken değiştirilemeyen bir kopyanın dışında çalışır.


15
2018-05-03 19:15



Bu sığ bir klon değil mi? Eğer T bir referans türüdür, bu sadece tüm orijinal nesnelere referanslar içeren yeni bir liste döndürmez mi? Durum böyleyse, liste yaklaşımı, liste nesnelerine, listenin farklı "kopyaları" aracılığıyla birden fazla iş parçacığı tarafından erişilebildiğinden, hala sorun çıkarmaya neden olabilir. - Joel B
Doğru, sığ bir kopyadır. Asıl amaç, üzerinde yineleme yapmak için güvenli olacak bir klonlanmış kümeye sahip olmaktı (böylece newListsayımı geçersiz kılan herhangi bir öğe eklenmemiş veya kaldırılmamıştır). - Tejs
_Lock statik mi olmalı? - Mike Ward
Başka bir düşünce. Bu uygulama, birden çok yazar için güvenli midir? Değilse, belki de ReadSafeList olarak adlandırılmalıdır. - Mike Ward
@MikeWard - Olmaması gerektiğini düşünmüyorum herşey örnek ne zaman kilitlenir herhangi örnek klonlanıyor! - Josh M.


Hatta kabul edilen cevap ConcurrentBag, Radek'in cevaba verdiği yorumun dediği gibi, her durumda listenin gerçek yerini aldığını sanmıyorum: "ConcurrentBag sıralanmamış koleksiyon, bu yüzden Listenin aksine sipariş vermeyi garanti etmiyor. Ayrıca indeksle öğelere erişemiyorsunuz ".

Yani, .NET 4.0 veya daha yüksek bir sürümünü kullanırsanız, bir geçici çözüm kullanmak olabilir ConcurrentDictionary dizi değeri olarak dizi dizini ve TValue olarak tamsayı TKey ile. Bu, Pluralsight'ın listesindeki listeyi değiştirmenin bir yoludur C # Eşzamanlı Koleksiyonlar ders adı ve tanımında. ConcurrentDictionary yukarıda belirtilen her iki problemi çözer: indeks erişimi ve siparişi (davlumbaz altında karma tablosu olduğu için sipariş verememekteyiz, ancak şu anki .NET uygulaması elemanların eklenmesi sırasına göre tasarruf sağlar).


4
2018-03-31 18:49



lütfen -1 nedenlerini belirtin - tytyryty
Oy vermedim ve IMO için bir sebep yok. Haklısınız, ancak bazı cevaplarda bu kavram daha önce belirtilmiştir. Bana göre, bilmediğim .NET 4.0'da yeni bir iş parçacığı güvenli koleksiyon vardı. Durum için Bag veya Koleksiyon kullandığınızdan emin değilsiniz. +1 - Xaqron
teşekkür ederim Xaqron! - tytyryty
Bu cevabın birkaç sorunu var: 1) ConcurrentDictionary sözlük değil, liste. 2) Kendi cevabınızı belirttiğiniz gibi, bir cevabın bildirilmesinde belirttiğiniz sebeple çelişen, düzenleyici sırasının düzenlenmesi garanti edilmez. 3) Bir link video İlgili alıntıları bu cevaba getirmeden (zaten lisanslarına uygun olmayabilir). - jpmc26


Kullanabilirsiniz:

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

Thread Safe ArrayLsit oluşturmak için


2
2017-10-08 18:24



Hangi dilde konuşuyorsun? - John Demetriou
Java? Bunu özlediğim birkaç özellikten biri. Ama genellikle şöyle yazılır: Collections.synchronizedList (new ArrayList ()); - Nick


Daha ilkel olanı da kullanabilirsiniz

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

hangi kilit kullanır (bu yazıya bakın C # Kilit bloğunda yeniden atanan bir nesneyi kilitleme).

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

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 de, operasyon dizisinin süresi boyunca (her operasyonda kilitlemek yerine) kilit alacaktır. Bunun anlamı çıktıların doğru parçalarda ortaya çıkması gerektiği anlamına gelir (bunun kullanımım harici bir işlemden ekrana çıktı almaktı)

ThreadSafeList + 'in sadeliği + saydamlığını gerçekten çok seviyorum.


1
2017-08-22 09:43





T Listesi için kaynak koduna bakarsanız (https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877Orada bir sınıf var olduğunu fark edeceksiniz (tabii ki iç - neden, Microsoft, niçin?!?!) SenkronizeList T. denir.

   [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 ben kullanarak daha iyi bir uygulama biliyorlardı düşünüyorum SemaphoreSlim oluşturulabilir, ama alamadım.


1
2017-08-12 19:46



+1 Bütün koleksiyonun kilitlenmesi (_rootHer erişimde (okuma / yazma) bunu yavaş bir çözüm yapar. Belki bu sınıfın iç kalması daha iyidir. - Xaqron


inanıyorum _list.ToList() sana bir kopyasını yapacak. Gerekirse, 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. Oh, ve evet, diğerlerinin belirttiği gibi set yöntemini kilitlemeniz gerekecek.


0
2018-05-03 19:12





İşte size sorduğunuz sınıf:

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

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

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

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

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

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

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

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

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

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

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

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

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

        public bool IsReadOnly { get { return false; } }

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

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

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

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

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

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

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

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

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

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

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

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

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

-1
2017-12-22 22:36



gerçek kodu yayınla - Cole Johnson
Sınıfı güncellerken Google Drive'daki sürüm güncellenir. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html - Protiguous
Niye ya this.GetEnumerator(); @Tejs önerdiğinde this.Clone().GetEnumerator();? - Cœur
Niye ya [DataContract( IsReference = true )]? - Cœur
En son sürüm şimdi GitHub'da! github.com/AIBrain/Librainian/blob/master/Collections/... - Protiguous


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

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

Burada ilginizi çekebilecek MSDN bir parçasıdır:

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

Bir liste, değiştirilmediği sürece aynı anda birden fazla okuyucuyu destekleyebilir. Bir koleksiyonda numaralandırma, özünde bir iş parçacığı güvenli prosedür değildir. Bir numaralamanın bir veya daha fazla yazma erişimi ile yarıştığı nadir durumlarda, iplik güvenliğini sağlamanın tek yolu, tüm numaralandırma sırasında koleksiyonu kilitlemektir. Koleksiyona, okuma ve yazma için çoklu iş parçacıklarının erişmesine izin vermek için kendi eşitlemenizi uygulamanız gerekir.


-3
2018-05-03 19:06



Hiç doğru değil. Concurrent setlerini kullanabilirsiniz. - ANeves