스레드로부터 안전한 List <T> 속성


122

List<T>의문의 여지없이 스레드에서 안전하게 사용할 수있는 속성으로 의 구현을 원합니다 .

이 같은:

private List<T> _list;

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

여전히 컬렉션의 복사본 (복제 된)을 반환해야하는 것 같으므로 어딘가에 컬렉션을 반복하고 동시에 컬렉션이 설정되면 예외가 발생하지 않습니다.

스레드로부터 안전한 컬렉션 속성을 구현하는 방법은 무엇입니까?


4
잠금을 사용하십시오.
— atoMerz 2011 년

스레드로부터 안전한 IList<T>(vs List<T>) 구현을 사용할 수 있습니까 ?
— Greg

2
SynchronizedCollection <T> 를 확인 했습니까 ?
— Saturn Technologies

사용 BlockingCollection 또는 ConcurrentDictionary
— 쿠마 chandraketu

재산 뒤에있는 물건으로 어떤 작업을해야합니까? List<T>구현하는 모든 것이 필요하지 않을 수 있습니까? 그렇다면 List<T>이미 가지고있는 모든 것에 대해 묻는 대신 필요한 인터페이스를 제공 할 수 있습니까?
— Victor Yarema

답변:


185

.Net 4를 대상으로하는 경우 System.Collections.Concurrent 네임 스페이스에 몇 가지 옵션이 있습니다.

ConcurrentBag<T>이 경우 대신 사용할 수 있습니다 .List<T>


5
List <T>와 마찬가지로 Dictionary와 달리 ConcurrentBag는 중복을 허용합니다.
— The Light

115
ConcurrentBag무순 컬렉션이므로 List<T>주문을 보증하는 것은 아닙니다. 또한 인덱스로 항목에 액세스 할 수 없습니다.
— Radek Stromský 2013 년

11
@ RadekStromský가 맞고, 순서가 지정된 동시 목록을 원하는 경우 ConcurrentQueue (FIFO) 또는 ConcurrentStack (LIFO)을 시도 할 수 있습니다.
— Caio Cunha


12
ConcurrentBag은은 IList를 구현하지 않습니다 실제로 목록의 안전 버전 스레드되지 않은
— Vasyl Zvarydchuk

87

가장 많은 표를 얻었음에도 불구하고 일반적으로 주문되지 않은 상태 (Radek Stromský가 이미 지적 했음) System.Collections.Concurrent.ConcurrentBag<T>를 스레드로부터 안전하게 교체 할 수 없습니다 System.Collections.Generic.List<T>.

하지만 System.Collections.Generic.SynchronizedCollection<T>프레임 워크의 .NET 3.0 부분부터 이미 호출 된 클래스 가 있지만, 거의 알려지지 않았고 아마 우연히 발견 한 적이없는 위치에 숨겨져 있습니다 (적어도 나는 결코하지 않았다).

SynchronizedCollection<T>System.ServiceModel.dll 어셈블리로 컴파일됩니다 (클라이언트 프로필의 일부이지만 이식 가능한 클래스 라이브러리에는 포함되지 않음).

도움이되기를 바랍니다.


3
나는 이것이 핵심 lib에 있지 않다고 외칩니다. : {간단한 동기화 된 컬렉션이 필요한 모든 것입니다.
— user2864740

이 옵션의 추가 도움이 토론 : stackoverflow.com/a/4655236/12484
— 존 슈나이더

2
System.Collections.Concurrent의 클래스를 위해 더 이상 사용되지 않기 때문에 숨겨져 있습니다.
— denfromufa

3
그리고 .NET의 핵심에서 사용할 수 없습니다
— denfromufa


17

샘플 ThreadSafeList 클래스를 만드는 것이 쉬울 것이라고 생각합니다.

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

열거자를 요청하기 전에 목록을 복제하기 만하면 실행 중에 수정할 수없는 복사본에서 열거가 작동합니다.


1
이것은 얕은 클론이 아닙니까? T참조 유형 인 경우 모든 원본 객체에 대한 참조를 포함하는 새 목록을 반환하지 않습니까? 이 경우 목록의 다른 "복사본"을 통해 여러 스레드에서 목록 개체에 액세스 할 수 있으므로이 방법은 여전히 ​​스레딩 문제를 일으킬 수 있습니다.
— Joel B

3
맞습니다. 얕은 사본입니다. 요점은 반복하기에 안전한 복제 된 집합을 갖는 것입니다 (따라서 newList열거자를 무효화하는 항목을 추가하거나 제거하지 않음).
— Tejs

7
_lock은 정적이어야합니까?
— Mike Ward

4
또 다른 생각. 이 구현은 여러 작성자에게 스레드 안전합니까? 그렇지 않은 경우 ReadSafeList라고해야합니다.
— Mike Ward

5
@MikeWard - 나는 그것이해야한다고 생각하지 않습니다 모든 인스턴스 때 잠 깁니다 모든 인스턴스가 복제되고 있습니다!
— Josh M.

11

수락 된 답변조차도 ConcurrentBag입니다. Radek의 답변에 대한 설명에서 "ConcurrentBag는 순서가 지정되지 않은 컬렉션이므로 List와 달리 주문을 보장하지 않습니다. 또한 인덱스로 항목에 액세스 할 수 없습니다. ".

따라서 .NET 4.0 이상을 사용하는 경우 해결 방법은 정수 TKey를 배열 인덱스로, TValue를 배열 값 으로 사용하는 ConcurrentDictionary 를 사용하는 것 입니다. 이것은 Pluralsight의 C # Concurrent Collections 과정 에서 목록을 바꾸는 권장 방법입니다 . ConcurrentDictionary는 위에서 언급 한 두 가지 문제를 모두 해결합니다. 인덱스 액세스 및 순서 지정 (내부에서 해시 테이블이므로 순서 지정에 의존 할 수 없지만 현재 .NET 구현은 요소 추가 순서를 저장합니다).


1
-1 이유 주시기 바랍니다
— tytyryty

나는 하향 투표하지 않았고 IMO에 대한 이유가 없습니다. 당신이 옳지 만 개념은 이미 일부 답변에서 언급되었습니다. 나에게 요점은 내가 알지 못했던 .NET 4.0에 새로운 스레드 안전 컬렉션이 있다는 것입니다. 상황에 따라 사용 된 가방 또는 컬렉션을 잘 모릅니다. +1
— Xaqron

2
이 답변에는 몇 가지 문제가 있습니다. 1) ConcurrentDictionary목록이 아니라 사전입니다. 2) 답변을 게시 한 이유와 모순되는 귀하의 답변에 명시된 바와 같이 보존 순서가 보장되지 않습니다. 3) 이 답변에 관련 인용문을 가져 오지 않고 비디오로 연결됩니다 (어쨌든 라이선스와 일치하지 않을 수 있음).
— jpmc26

current implementation문서에서 명시 적으로 보장하지 않는 경우 와 같은 것에 의존 할 수 없습니다 . 구현은 예고없이 언제든지 변경 될 수 있습니다.
— Victor Yarema 2018

@ jpmc26, 예, 물론 List를 완전히 대체하지는 않지만 ConcurrentBag도 허용되는 답변으로 동일합니다. List의 엄격한 대체는 아니지만 해결 방법입니다. 우려 사항에 답하려면 : 1) ConcurrentDictionary는 올바른 목록이 아닌 사전이지만 목록에는 배열 뒤에 배열이 있습니다.이 배열은 키로 int를 사용하여 사전과 동일한 O (1)에서 색인화 할 수 있습니다. 2) 예 순서는 doc ( 비록 그것이 보존 되더라도), 그러나 받아 들여졌다 ConcurrentBag는 멀티 스레드 시나리오에서도 순서를 보장 할 수 없다
— tytyryty

9

C #의 ArrayList클래스에는 Synchronized메서드가 있습니다.

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

이것은 모든 인스턴스 주위에 스레드 안전 래퍼를 반환합니다 IList. 스레드 안전성을 보장하기 위해 모든 작업은 래퍼를 통해 수행되어야합니다.


1
무슨 언어에 대해 말하고 있습니까?
— John Demetriou

자바? 내가 놓친 몇 가지 기능 중 하나입니다. 그러나 일반적으로 다음과 같이 작성됩니다. Collections.synchronizedList (new ArrayList ());
— Nick

2
System.Collections를 사용하고 있거나 var System.Collections.ArrayList.Synchronized (new System.Collections.ArrayList ());를 사용할 수 있다고 가정하면 유효한 C #입니다.
— user2163234

5

List of T의 소스 코드 ( https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 )를 보면 거기에 클래스가 있음을 알 수 있습니다 (물론 내부-왜, Microsoft, 왜?!?!)가 SynchronizedList of T라고 불렀습니다. 여기에 코드를 복사하여 붙여 넣습니다.

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

개인적으로 나는 그들이 SemaphoreSlim을 사용하여 더 나은 구현을 만들 수 있다는 것을 알고 있다고 생각 하지만 그것을 얻지 못했습니다.


2
+1 _root각 액세스 (읽기 / 쓰기)에서 전체 컬렉션 ( )을 잠그면 느린 솔루션이됩니다. 이 클래스가 내부에 남아있는 것이 더 낫습니다.
— Xaqron

3
이 구현은 스레드로부터 안전하지 않습니다. 여전히 "System.InvalidOperationException : '컬렉션이 수정되었습니다. 열거 작업이 실행되지 않을 수 있습니다.'"가 발생합니다.
— Raman Zhylich

2
이는 스레드 안전성과 관련이 없지만 컬렉션을 반복하고 변경한다는 사실과 관련이 있습니다. 목록이 변경된 것을 볼 때 열거자가 예외를 throw합니다. 이 문제를 해결하려면 고유 한 IEnumerator를 구현하거나 동일한 컬렉션을 동시에 반복하고 변경하지 않도록 코드를 변경해야합니다.
— Siderite Zackwehdex 2011

"동기화 된"메서드 중에 컬렉션 이 변경 될 수 있으므로 스레드로부터 안전하지 않습니다 . 즉 절대적 이다 스레드 안전의 일부입니다. Clear()다른 호출 후 this[index]잠금이 활성화되기 전에 한 스레드 호출 을 고려하십시오 . index더 이상 사용하기에 안전하지 않으며 마지막으로 실행될 때 예외가 발생합니다.
— Suncat2000

2

더 원시적 인 것을 사용할 수도 있습니다.

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

어떤 잠금을 사용하는지 ( 잠금 블록에 재 할당 된 개체 잠금 이 게시물 참조 ).

코드에서 예외가 예상되는 경우 안전하지 않지만 다음과 같은 작업을 수행 할 수 있습니다.

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

이것에 대한 좋은 점 중 하나는 일련의 작업 동안 (각 작업을 잠그는 대신) 잠금을 얻게된다는 것입니다. 즉, 출력이 올바른 청크로 나와야 함을 의미합니다 (내 사용은 외부 프로세스에서 화면에 출력을 가져 오는 것입니다)

나는 충돌을 멈추는 데 중요한 역할을하는 ThreadSafeList +의 단순성 + 투명성을 정말 좋아합니다.


2

.NET Core (모든 버전)에서 .NET Core의 모든 기능이있는 ImmutableList 를 사용할 수 있습니다 List<T>.


1

나는 _list.ToList()당신에게 사본을 만들 것이라고 믿습니다 . 다음과 같이 필요한 경우 쿼리 할 수도 있습니다.

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

어쨌든 msdn은 이것이 단순히 참조가 아니라 실제로 사본이라고 말합니다. 아, 그리고 네, 다른 사람들이 지적한 것처럼 set 메서드를 고정해야합니다.


1

이것을 발견하는 많은 사람들이 스레드로부터 안전한 색인 동적 크기의 컬렉션을 원하는 것 같습니다. 내가 아는 가장 가깝고 쉬운 것은 일 것입니다.

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

정상적인 인덱싱 동작을 원할 경우 키가 적절하게 식별되었는지 확인해야합니다. 주의하면 .count가 추가하는 새 키 값 쌍의 키로 충분할 수 있습니다.


1
키의 잘못이 아닌데 왜 키를 유죄해야합니까?
— Suncat2000

@ suncat2000 하!
— Richard II

1

나는 다루는 사람이 제안 List<T>에 살펴보고 멀티 스레딩 시나리오를 불변의 컬렉션 특정의 ImmutableArray .

다음과 같은 경우 매우 유용합니다.

  1. 목록에서 상대적으로 적은 항목
  2. 읽기 / 쓰기 작업이 많지 않음
  3. 많은 동시 액세스 (즉, 읽기 모드에서 목록에 액세스하는 많은 스레드)

또한 일종의 트랜잭션과 유사한 동작을 구현해야 할 때 유용 할 수 있습니다 (예 : 실패시 삽입 / 업데이트 / 삭제 작업 되돌리기).


-1

요청한 수업은 다음과 같습니다.

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 드라이브의 버전이 업데이트됩니다. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html
— Protiguous 2012-12-28

this.GetEnumerator();@Tejs가 제안하는 이유 는 this.Clone().GetEnumerator();무엇입니까?
— Cœur

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

최신 버전이 GitHub에 있습니다! github.com/AIBrain/Librainian/blob/master/Collections/...
— Protiguous

Add () 메서드에서 두 개의 작은 버그를 발견하고 수정했습니다. 참고로.
— Protiguous

-3

기본적으로 안전하게 열거하려면 잠금을 사용해야합니다.

이에 대해서는 MSDN을 참조하십시오. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

다음은 관심을 가질만한 MSDN의 일부입니다.

이 형식의 Public static (Visual Basic에서 공유) 멤버는 스레드로부터 안전합니다. 모든 인스턴스 멤버는 스레드 안전이 보장되지 않습니다.

컬렉션이 수정되지 않는 한 List는 동시에 여러 리더를 지원할 수 있습니다. 컬렉션을 통한 열거는 본질적으로 스레드로부터 안전한 프로 시저가 아닙니다. 드물게 열거가 하나 이상의 쓰기 액세스와 충돌하는 경우 스레드 안전성을 보장하는 유일한 방법은 전체 열거 중에 컬렉션을 잠그는 것입니다. 읽기 및 쓰기를 위해 여러 스레드에서 컬렉션에 액세스 할 수 있도록하려면 고유 한 동기화를 구현해야합니다.


2
전혀 사실이 아닙니다. 동시 세트를 사용할 수 있습니다.
— ANeves

-3

다음은 잠금이없는 스레드 허용 목록에 대한 클래스입니다.

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

스레드
— 세이프

_i ++는 스레드 세이프가 아닙니다. 당신은 그것을 증가시킬 때 원자 추가를 사용해야하고 아마도 그것을 휘발성으로 표시해야합니다. CheckReset ()은 스레드 세이프가 아닙니다. 조건부 확인과 Reset () 호출 사이에 모든 것이 발생할 수 있습니다. 자신 만의 멀티 스레딩 유틸리티를 작성하지 마십시오.
— Chris Rollins

-15

이렇게하려면 lock문을 사용하십시오 . ( 자세한 내용은 여기를 참조하십시오. )

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

참고로 이것은 아마도 당신의 요구와 정확히 일치하지 않을 것입니다. 당신은 당신의 코드에서 더 멀리 잠그고 싶을 것 같지만 나는 그것을 가정 할 수 없습니다. 보세요lock키워드를 특정 상황에 맞게 사용하십시오.

필요한 경우 읽기 / 쓰기가 동시에 발생하지 않도록 변수를 사용하여 및 블록 lock모두에서 할 수 있습니다.getset_list


1
그것은 그의 문제를 해결하지 못할 것입니다. 스레드가 목록에 추가되지 않고 참조 설정을 중지 할뿐입니다.
— Tejs

그리고 한 스레드가 값을 설정하고 다른 스레드가 컬렉션을 반복하면 어떻게 될까요 (귀하의 코드로 가능합니다).
— Xaqron 2011 년

내가 말했듯이 잠금은 코드에서 더 멀리 옮겨야 할 것입니다. 이것은 lock 문을 사용하는 방법의 예일뿐입니다.
— Josh M.

2
@Joel Mueller : 그렇습니다. 나는 단지 질문자가 lock진술을 조사해야한다는 것을 설명하려고 노력하고 있습니다. 비슷한 예를 사용하여 거의 노력없이 응용 프로그램을 교착 상태로 만들 수 있으므로 for 루프를 사용해서는 안된다고 주장 할 수 있습니다.for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
— Josh M.

5
나는 당신의 코드가 즉각적인 교착 상태를 의미한다고 주장한 적이 없습니다. 다음과 같은 이유로이 특정 질문에 대한 잘못된 대답입니다. 1) 목록을 열거하는 동안 수정되는 목록의 내용이나 한 번에 두 개의 스레드에 의해 보호되지 않습니다. 2) setter를 잠그고 getter를 잠그면 속성이 실제로 스레드로부터 안전하지 않음을 의미합니다. 3) 클래스 외부에서 액세스 할 수 있는 모든 참조를 잠그는 것은 실수로 교착 상태가 발생할 가능성을 극적으로 증가시키기 때문에 일반적으로 나쁜 습관으로 간주됩니다. 의는 이유입니다 lock (this)및 lock (typeof(this))큰 노 더의 없습니다.
— Joel Mueller
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.