스레드로부터 안전한 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.