스레드로부터 안전한 목록 특성
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 구현은 요소 추가 순서를 저장합니다).
당신이 사용할 수있는:
var threadSafeArrayList = ArrayList.Synchronized(new ArrayList());
스레드 안전 ArrayLsit을 만드는 방법
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 +의 단순성 + 투명성을 정말 좋아합니다.
나는 _list.ToList()
당신에게 사본을 만들 것이라고 믿습니다 . 다음과 같이 필요한 경우 쿼리 할 수도 있습니다.
_list.Select("query here").ToList();
어쨌든 msdn은 이것이 단순히 참조가 아니라 실제로 사본이라고 말합니다. 아, 그리고 네, 다른 사람들이 지적한 것처럼 set 메서드를 고정해야합니다.
이것을 발견하는 많은 사람들이 스레드로부터 안전한 색인 동적 크기의 컬렉션을 원하는 것 같습니다. 내가 아는 가장 가깝고 쉬운 것은 일 것입니다.
System.Collections.Concurrent.ConcurrentDictionary<int, YourDataType>
정상적인 인덱싱 동작을 원할 경우 키가 적절하게 식별되었는지 확인해야합니다. 주의하면 .count가 추가하는 새 키 값 쌍의 키로 충분할 수 있습니다.
나는 다루는 사람이 제안 List<T>
에 살펴보고 멀티 스레딩 시나리오를 불변의 컬렉션 특정의 ImmutableArray .
다음과 같은 경우 매우 유용합니다.
- 목록에서 상대적으로 적은 항목
- 읽기 / 쓰기 작업이 많지 않음
- 많은 동시 액세스 (즉, 읽기 모드에서 목록에 액세스하는 많은 스레드)
또한 일종의 트랜잭션과 유사한 동작을 구현해야 할 때 유용 할 수 있습니다 (예 : 실패시 삽입 / 업데이트 / 삭제 작업 되돌리기).
요청한 수업은 다음과 같습니다.
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 );
}
}
}
}
}
}
기본적으로 안전하게 열거하려면 잠금을 사용해야합니다.
이에 대해서는 MSDN을 참조하십시오. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx
다음은 관심을 가질만한 MSDN의 일부입니다.
이 형식의 공용 정적 (Visual Basic에서 공유) 멤버는 스레드로부터 안전합니다. 모든 인스턴스 멤버는 스레드 안전이 보장되지 않습니다.
컬렉션이 수정되지 않는 한 List는 동시에 여러 리더를 지원할 수 있습니다. 컬렉션을 통한 열거는 본질적으로 스레드로부터 안전한 프로 시저가 아닙니다. 드물게 열거가 하나 이상의 쓰기 액세스와 충돌하는 경우 스레드 안전성을 보장하는 유일한 방법은 전체 열거 동안 컬렉션을 잠그는 것입니다. 읽기 및 쓰기를 위해 여러 스레드에서 컬렉션에 액세스 할 수 있도록하려면 고유 한 동기화를 구현해야합니다.
이렇게하려면 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
에 둘 수 있습니다.get
set
_list
참고 URL : https://stackoverflow.com/questions/5874317/thread-safe-listt-property
'programing tip' 카테고리의 다른 글
MySQL 루트 사용자의 전체 권한을 어떻게 복원 할 수 있습니까? (0) | 2020.08.11 |
---|---|
화면의 모든 위치에 AlertDialog 표시 (0) | 2020.08.11 |
AngularJS의 여러 특정 모델 속성으로 필터링 (OR 관계) (0) | 2020.08.11 |
Pandas로 txt에서 데이터로드 (0) | 2020.08.11 |
미학과 geom_text를 사용할 때 범례에서 'a'제거 (0) | 2020.08.10 |