คุณสมบัติ Thread-safe List <T>


123

ฉันต้องการนำไปใช้List<T>เป็นคุณสมบัติที่สามารถใช้เธรดได้อย่างปลอดภัยโดยไม่ต้องสงสัย

สิ่งนี้:

private List<T> _list;

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

ดูเหมือนว่าฉันยังต้องส่งคืนสำเนา (โคลน) ของคอลเลกชันดังนั้นหากมีที่ไหนสักแห่งที่เรากำลังทำซ้ำคอลเลกชันและในเวลาเดียวกันก็มีการตั้งค่าคอลเล็กชันจะไม่มีการเพิ่มข้อยกเว้น

วิธีใช้คุณสมบัติการรวบรวมเธรดที่ปลอดภัย


4
ใช้ล็อคที่ควรทำ
atoMerz

สามารถใช้การใช้งานเธรดปลอดภัยของIList<T>(vs List<T>) ได้หรือไม่
Greg

2
คุณตรวจสอบSynchronizedCollection <T> แล้วหรือยัง
Saturn Technologies

ใช้ BlockingCollection หรือ ConcurrentDictionary
kumar chandraketu

คุณต้องดำเนินการอะไรกับวัตถุที่อยู่เบื้องหลังทรัพย์สิน? เป็นไปได้ไหมว่าคุณไม่ต้องการทุกอย่างที่List<T>ใช้? ถ้าใช่คุณช่วยระบุอินเทอร์เฟซที่คุณต้องการแทนการถามเกี่ยวกับทุกสิ่งที่List<T>มีอยู่แล้วได้ไหม
Victor Yarema

คำตอบ:


186

หากคุณกำลังกำหนดเป้าหมาย. Net 4 มีตัวเลือกสองสามตัวในSystem.Collections.Concurrent Namespace

คุณสามารถใช้ConcurrentBag<T>ในกรณีนี้แทนList<T>


5
เช่นเดียวกับรายการ <T> และไม่เหมือนพจนานุกรม ConcurrentBag ยอมรับรายการที่ซ้ำกัน
The Light

115
ConcurrentBagเป็นคอลเลกชันที่ไม่มีการเรียงลำดับดังนั้นจึงList<T>ไม่รับประกันการสั่งซื้อ นอกจากนี้คุณไม่สามารถเข้าถึงรายการด้วยดัชนี
Rathk Stromský

11
@ RadekStromskýเป็นสิทธิและในกรณีที่คุณต้องการที่จะสั่งรายการพร้อมกันคุณอาจจะลองConcurrentQueue (FIFO)หรือConcurrentStack (LIFO)
Caio Cunha


12
ConcurrentBag ไม่ได้ใช้ IList และไม่ใช่รุ่นที่ปลอดภัยของรายการ
Vasyl Zvarydchuk

87

แม้ว่าจะได้รับคะแนนโหวตมากที่สุด แต่ก็มักจะไม่สามารถใช้System.Collections.Concurrent.ConcurrentBag<T>แทนเธรดที่ปลอดภัยได้ตามที่System.Collections.Generic.List<T>เป็นอยู่ (ราเด็คสตรอมสค์ชี้ให้เห็นแล้ว) ไม่ได้รับคำสั่ง

แต่มีคลาสที่เรียกSystem.Collections.Generic.SynchronizedCollection<T>ว่าตั้งแต่. NET 3.0 เป็นส่วนหนึ่งของเฟรมเวิร์กแล้ว แต่มันถูกซ่อนไว้อย่างดีในตำแหน่งที่ไม่มีใครคาดคิดว่ามันจะไม่ค่อยมีใครรู้จักและคุณอาจไม่เคยสะดุดมันมาก่อน (อย่างน้อย ฉันไม่เคยทำ).

SynchronizedCollection<T>ถูกรวบรวมลงในแอสเซมบลีSystem.ServiceModel.dll (ซึ่งเป็นส่วนหนึ่งของโปรไฟล์ไคลเอนต์ แต่ไม่ใช่ของไลบรารีคลาสพกพา)

หวังว่าจะช่วยได้


3
ฉันร้องไห้ว่าไม่ได้อยู่ใน core lib: {คอลเลกชันที่ซิงโครไนซ์แบบธรรมดามักเป็นสิ่งที่จำเป็น
user2864740

การสนทนาที่เป็นประโยชน์เพิ่มเติมเกี่ยวกับตัวเลือกนี้: stackoverflow.com/a/4655236/12484
Jon Schneider

2
มันถูกซ่อนไว้อย่างดีเนื่องจากเลิกใช้แล้วซึ่งเป็นประโยชน์ต่อคลาสใน System.Collections.Concurrent
denfromufa

3
และไม่มีใน. net core
denfromufa

2
@denfromufa ดูเหมือนว่าพวกเขาเพิ่มสิ่งนี้ใน. net core 2.0 docs.microsoft.com/en-gb/dotnet/api/…
Cirelli94

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 ฉันไม่คิดว่าจะเป็นการแทนที่รายการจริงในทุกกรณีดังที่ความคิดเห็นของ Rathk ต่อคำตอบกล่าวว่า: "ConcurrentBag เป็นคอลเล็กชันที่ไม่มีการเรียงลำดับดังนั้นจึงไม่เหมือนกับรายการที่ไม่รับประกันการสั่งซื้อนอกจากนี้คุณไม่สามารถเข้าถึงรายการด้วยดัชนี "

ดังนั้นถ้าคุณใช้. NET 4.0 หรือสูงกว่าวิธีแก้ปัญหาอาจใช้ConcurrentDictionaryกับ TKey จำนวนเต็มเป็นดัชนีอาร์เรย์และ TValue เป็นค่าอาร์เรย์ นี่คือวิธีที่แนะนำในการเปลี่ยนรายการใน Pluralsight ของหลักสูตร C # พร้อมกันคอลเลกชัน ConcurrentDictionary แก้ปัญหาทั้งสองที่กล่าวถึงข้างต้น: การเข้าถึงดัชนีและการสั่งซื้อ (เราไม่สามารถพึ่งพาการสั่งซื้อได้เนื่องจากเป็นตารางแฮชภายใต้ประทุน แต่การใช้งาน. NET ในปัจจุบันจะบันทึกลำดับของการเพิ่มองค์ประกอบ)


1
โปรดระบุเหตุผลที่ -1
tytyryty

ฉันไม่ได้ลงคะแนนและไม่มีเหตุผลสำหรับ IMO คุณพูดถูก แต่แนวคิดนี้ได้กล่าวไว้แล้วในบางคำตอบ สำหรับฉันประเด็นคือมีคอลเล็กชันเธรดที่ปลอดภัยใหม่ใน. NET 4.0 ซึ่งฉันไม่ทราบ ไม่แน่ใจว่ากระเป๋าหรือคอลเลคชันใช้แล้วกับสถานการณ์ +1
Xaqron

2
คำตอบนี้มีปัญหาหลายประการ: 1) ConcurrentDictionaryเป็นพจนานุกรมไม่ใช่รายการ 2) ไม่รับประกันว่าจะสงวนคำสั่งเนื่องจากคำตอบของคุณระบุไว้ซึ่งขัดแย้งกับเหตุผลที่คุณระบุไว้ในการโพสต์คำตอบ 3) ลิงก์ไปยังวิดีโอโดยไม่นำคำพูดที่เกี่ยวข้องมาเป็นคำตอบนี้ (ซึ่งอาจไม่สอดคล้องกับการให้ใบอนุญาตของพวกเขาอยู่ดี)
jpmc26

คุณไม่สามารถพึ่งพาสิ่งต่างๆเช่นcurrent implementationหากเอกสารไม่ได้รับการรับรองอย่างชัดเจน การใช้งานอาจเปลี่ยนแปลงได้ตลอดเวลาโดยไม่ต้องแจ้งให้ทราบล่วงหน้า
Victor Yarema

@ jpmc26 ใช่มันไม่ใช่การแทนที่รายการทั้งหมดแน่นอน แต่ก็เหมือนกันกับ ConcurrentBag เป็นคำตอบที่ยอมรับ - ไม่ใช่การแทนที่รายการอย่างเข้มงวด แต่ควรหลีกเลี่ยง เพื่อตอบข้อกังวลของคุณ: 1) ConcurrentDictionary เป็นพจนานุกรมที่ไม่ใช่รายการที่คุณพูดถูกอย่างไรก็ตามรายการมีอาร์เรย์อยู่ข้างหลังซึ่งสามารถจัดทำดัชนีใน O (1) เหมือนกับพจนานุกรมที่มี int เป็นคีย์ 2) คำสั่งใช่ไม่รับประกันโดย doc ( แม้ว่าจะถูกเก็บรักษาไว้) แต่ ConcurrentBag ที่ยอมรับก็ไม่สามารถรับประกันคำสั่งซื้อได้เช่นกันในสถานการณ์แบบมัลติเธรด
tytyryty

9

คลาส C # ArrayListมีSynchronizedวิธีการ

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

นี้จะส่งกลับด้ายห่อหุ้มความปลอดภัยรอบ ๆ IListตัวอย่างของการใด การดำเนินการทั้งหมดต้องดำเนินการผ่านกระดาษห่อหุ้มเพื่อความปลอดภัยของเธรด


1
คุณกำลังพูดถึงภาษาอะไร?
John Demetriou

Java? หนึ่งในคุณสมบัติไม่กี่อย่างที่ฉันคิดถึง แต่มักจะเขียนเป็น: Collections.synchronizedList (new ArrayList ());
นิค

2
นี่คือ C # ที่ถูกต้องโดยสมมติว่าคุณใช้ System.Collections หรือคุณสามารถใช้ var System.Collections.ArrayList.Synchronized (System.Collections.ArrayList ใหม่ ());
user2163234

5

หากคุณดูซอร์สโค้ดสำหรับ List of T ( https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 ) คุณจะสังเกตเห็นว่ามีคลาสอยู่ที่นั่น (ซึ่งแน่นอน ภายใน - ทำไม Microsoft ทำไม!?!) เรียกว่า SynchronizedList ของ 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: 'Collection was modified; enumeration operation may not execute."
Raman Zhylich

2
สิ่งนี้ไม่เกี่ยวข้องกับความปลอดภัยของเธรด แต่เป็นความจริงที่ว่าคุณกำลังทำซ้ำและเปลี่ยนคอลเลคชัน ตัวแจงนับเป็นข้อยกเว้นเมื่อเห็นว่ารายการมีการเปลี่ยนแปลง ในการหลีกเลี่ยงปัญหานี้คุณต้องติดตั้ง IEnumerator ของคุณเองหรือเปลี่ยนรหัสเพื่อไม่ให้วนซ้ำและเปลี่ยนคอลเลคชันเดียวกันในเวลาเดียวกัน
Siderite Zackwehdex

ไม่ปลอดภัยต่อเธรดเนื่องจากคอลเล็กชันสามารถเปลี่ยนแปลงได้ในระหว่างวิธีการ "ซิงโครไนซ์" นั่นเป็นส่วนหนึ่งของความปลอดภัยของด้ายอย่างแน่นอน พิจารณาการโทรหนึ่งเธรดClear()หลังจากการโทรอื่นthis[index]แต่ก่อนที่จะเปิดใช้งานการล็อก indexไม่ปลอดภัยที่จะใช้อีกต่อไปและจะเกิดข้อยกเว้นเมื่อดำเนินการในที่สุด
Suncat2000

2

คุณยังสามารถใช้แบบดั้งเดิมได้อีกด้วย

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

ล็อคที่ใช้ (ดูโพสต์นี้C # การล็อกวัตถุที่กำหนดใหม่ในบล็อกล็อก )

หากคุณคาดว่าจะมีข้อยกเว้นในโค้ดสิ่งนี้ไม่ปลอดภัย แต่อนุญาตให้คุณทำสิ่งต่อไปนี้:

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 + ที่ทำหน้าที่สำคัญในการหยุดข้อขัดข้อง



1

ฉันเชื่อว่า_list.ToList()จะทำให้คุณเป็นสำเนา นอกจากนี้คุณยังสามารถสอบถามได้หากต้องการเช่น:

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

อย่างไรก็ตาม msdn กล่าวว่านี่เป็นสำเนาและไม่ใช่แค่ข้อมูลอ้างอิง อ้อและใช่คุณจะต้องล็อคใน set method ตามที่คนอื่น ๆ ชี้ไว้


1

ดูเหมือนว่าหลาย ๆ คนที่พบสิ่งนี้ต้องการให้มีการจัดทำดัชนีคอลเลกชันขนาดไดนามิกที่ปลอดภัย สิ่งที่ใกล้เคียงที่สุดและง่ายที่สุดที่ฉันรู้คือ

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

สิ่งนี้ต้องการให้คุณตรวจสอบให้แน่ใจว่าคีย์ของคุณถูกกล่าวหาอย่างเหมาะสมหากคุณต้องการให้มีพฤติกรรมการจัดทำดัชนีตามปกติ หากคุณระวัง. นับอาจเพียงพอสำหรับคีย์สำหรับคู่ค่าคีย์ใหม่ที่คุณเพิ่ม


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

ทำไมthis.GetEnumerator();เมื่อ @Tejs แนะนำthis.Clone().GetEnumerator();?
Cœur

ทำไม[DataContract( IsReference = true )]?
Cœur

เวอร์ชันล่าสุดอยู่ใน GitHub แล้ว! github.com/AIBrain/Librainian/blob/master/Collections/…
Protiguous

ฉันพบและแก้ไขข้อบกพร่องเล็ก ๆ น้อย ๆ สองข้อในวิธีการเพิ่ม () FYI
ประท้วง

-3

โดยทั่วไปหากคุณต้องการแจกแจงอย่างปลอดภัยคุณต้องใช้การล็อก

โปรดดู MSDN เกี่ยวกับเรื่องนี้ http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

นี่คือส่วนหนึ่งของ MSDN ที่คุณอาจสนใจ:

สมาชิกแบบคงที่สาธารณะ (ที่ใช้ร่วมกันใน Visual Basic) ประเภทนี้เป็นเธรดที่ปลอดภัย ไม่รับประกันว่าสมาชิกอินสแตนซ์ใด ๆ จะปลอดภัย

รายการสามารถรองรับผู้อ่านหลายคนพร้อมกันได้ตราบเท่าที่ไม่มีการแก้ไขคอลเล็กชัน การแจงนับผ่านคอลเลกชันไม่ใช่ขั้นตอนที่ปลอดภัยต่อเธรด ในกรณีที่ไม่ค่อยเกิดขึ้นซึ่งการแจงนับขัดแย้งกับการเข้าถึงการเขียนตั้งแต่หนึ่งรายการขึ้นไปวิธีเดียวที่จะมั่นใจได้ว่าเธรดปลอดภัยคือการล็อกคอลเลกชันระหว่างการแจงนับทั้งหมด ในการอนุญาตให้หลายเธรดเข้าถึงคอลเลกชันสำหรับการอ่านและการเขียนคุณต้องใช้การซิงโครไนซ์ของคุณเอง


2
ไม่เป็นความจริง แต่อย่างใด คุณสามารถใช้ชุดพร้อมกันได้
อังคาร

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

นี่ไม่ใช่เธรดปลอดภัย
Aldracor

_i ++ ไม่ใช่เธรดที่ปลอดภัย คุณต้องใช้การเพิ่มอะตอมเมื่อคุณเพิ่มและอาจทำเครื่องหมายว่าระเหยด้วย CheckReset () ไม่ใช่เธรดที่ปลอดภัย ทุกอย่างสามารถเกิดขึ้นได้ระหว่างการตรวจสอบเงื่อนไขและเรียกไปที่รีเซ็ต () อย่าเขียนยูทิลิตี้มัลติเธรดของคุณเอง
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;
        }
    }
}

FYI นี่อาจไม่ใช่สิ่งที่คุณถาม - คุณอาจต้องการล็อครหัสของคุณให้ไกลขึ้น แต่ฉันไม่สามารถสรุปได้ ดูlockคำหลักและปรับแต่งการใช้งานให้เข้ากับสถานการณ์เฉพาะของคุณ

หากคุณต้องการคุณสามารถทำได้lockทั้งในgetและsetบล็อกโดยใช้_listตัวแปรซึ่งจะทำให้การอ่าน / เขียนไม่สามารถเกิดขึ้นพร้อมกันได้


1
นั่นจะไม่ช่วยแก้ปัญหาของเขาได้ เพียงหยุดเธรดจากการตั้งค่าการอ้างอิงไม่เพิ่มลงในรายการ
Tejs

และจะเกิดอะไรขึ้นถ้าเธรดหนึ่งตั้งค่าในขณะที่อีกเธรดหนึ่งกำลังวนซ้ำคอลเลกชัน (เป็นไปได้ด้วยรหัสของคุณ)
Xaqron

อย่างที่ฉันบอกไปว่าการล็อคอาจจะต้องถูกย้ายออกไปอีกในรหัส นี่เป็นเพียงตัวอย่างของการใช้คำสั่งล็อค
Josh M.

2
@ Joel Mueller: แน่นอนว่าถ้าคุณผลิตตัวอย่างโง่ ๆ แบบนั้น ฉันแค่พยายามแสดงให้เห็นว่าผู้ถามควรพิจารณาlockข้อความนั้น การใช้ตัวอย่างที่คล้ายกันฉันสามารถโต้แย้งได้ว่าเราไม่ควรใช้สำหรับลูปเนื่องจากคุณสามารถหยุดชะงักแอปพลิเคชันได้โดยแทบไม่ต้องใช้ความพยายามใด ๆ :for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Josh M.

5
ฉันไม่เคยอ้างว่ารหัสของคุณหมายถึงการหยุดชะงักในทันที เป็นคำตอบที่ไม่ดีสำหรับคำถามนี้ด้วยเหตุผลดังต่อไปนี้: 1) ไม่ได้ป้องกันเนื้อหาของรายการที่ถูกแก้ไขระหว่างการแจงนับรายการหรือสองเธรดพร้อมกัน 2) การล็อคตัวเซ็ตเตอร์ แต่ไม่ใช่ตัวรับหมายความว่าคุณสมบัติไม่ปลอดภัยต่อเธรดจริงๆ 3) การล็อกข้อมูลอ้างอิงใด ๆที่สามารถเข้าถึงได้จากภายนอกชั้นเรียนถือเป็นการปฏิบัติที่ไม่ดีเนื่องจากจะเพิ่มโอกาสในการหยุดชะงักโดยไม่ได้ตั้งใจอย่างมาก นั่นเป็นเหตุผลlock (this)และlock (typeof(this))เป็นเรื่องใหญ่
Joel Mueller
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.