[C#] 스레드로부터 안전한 List <T> 속성

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

이 같은:

private List<T> _list;

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

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

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



답변

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

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


답변

가장 많은 표를 얻었음에도 불구하고 일반적으로 주문되지 않은 상태 (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 어셈블리로 컴파일됩니다 (클라이언트 프로필의 일부이지만 이식 가능한 클래스 라이브러리에는 포함되지 않음).

도움이되기를 바랍니다.


답변

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

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


답변

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

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


답변

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

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

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


답변

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을 사용하여 더 나은 구현을 만들 수 있다는 것을 알고 있다고 생각 하지만 그것을 얻지 못했습니다.


답변

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

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 +의 단순성 + 투명성을 정말 좋아합니다.