HashSet พร้อมกันใน. NET Framework


151

ฉันมีคลาสต่อไปนี้

class Test{
    public HashSet<string> Data = new HashSet<string>();
}

ฉันต้องการเปลี่ยนฟิลด์ "ข้อมูล" จากเธรดที่แตกต่างกันดังนั้นฉันต้องการความคิดเห็นบางส่วนเกี่ยวกับการใช้งานเธรดที่ปลอดภัยในปัจจุบัน

class Test{
    public HashSet<string> Data = new HashSet<string>();

    public void Add(string Val){
            lock(Data) Data.Add(Val);
    }

    public void Remove(string Val){
            lock(Data) Data.Remove(Val);
    }
}

มีวิธีแก้ปัญหาที่ดีกว่าไปยังฟิลด์โดยตรงและป้องกันไม่ให้เข้าถึงหลายเธรดพร้อมกันหรือไม่


วิธีการเกี่ยวกับการใช้หนึ่งในคอลเลกชันภายใต้System.Collections.Concurrent
I4V

8
แน่นอนทำให้เป็นส่วนตัว
Hans Passant

3
จากมุมมองที่เกิดขึ้นพร้อมกันฉันไม่เห็นอะไรผิดปกติกับสิ่งที่คุณทำนอกเหนือไปจากเขตข้อมูลที่เป็นสาธารณะ! คุณจะได้ประสิทธิภาพการอ่านที่ดีขึ้นโดยใช้ ReaderWriterLockSlim หากเป็นข้อกังวล msdn.microsoft.com/en-us/library/…
อัลลันผู้อาวุโส

@AllanElder ReaderWriterLockจะเป็นประโยชน์ (มีประสิทธิภาพ) เมื่อผู้อ่านหลายคนและนักเขียนคนเดียว เราจะรู้ว่านี่เป็นกรณีของ OP
Sriram Sakthivel

2
การใช้งานในปัจจุบันไม่ได้เป็น 'พร้อมกัน' จริงๆ :) มันเป็นแค่เธรดที่ปลอดภัย
ไม่ได้กำหนด

คำตอบ:


164

การใช้งานของคุณถูกต้อง .NET Framework ไม่ได้ให้ประเภทแฮชเซ็ตที่เกิดขึ้นพร้อมกันในตัวขออภัย อย่างไรก็ตามมีวิธีแก้ไขปัญหาบางอย่าง

ConcurrentDictionary (แนะนำ)

นี้คนแรกคือการใช้ในชั้นเรียนConcurrentDictionary<TKey, TValue>ในการ System.Collections.Concurrentnamespace ในกรณีนี้ค่าไม่มีจุดหมายดังนั้นเราสามารถใช้วิbyte(หน่วยความจำ 1 ไบต์)

private ConcurrentDictionary<string, byte> _data;

นี่คือตัวเลือกที่แนะนำเนื่องจากประเภทนี้ปลอดภัยสำหรับเธรดและให้ประโยชน์เหมือนกันHashSet<T>ยกเว้นคีย์และค่าเป็นวัตถุอื่น

ที่มา: Social MSDN

ConcurrentBag

ถ้าคุณไม่สนใจรายการที่ซ้ำกันคุณสามารถใช้คลาสConcurrentBag<T>ในเนมสเปซเดียวกันของคลาสก่อนหน้า

private ConcurrentBag<string> _data;

ดำเนินการเอง

ในที่สุดตามที่คุณทำคุณสามารถใช้ชนิดข้อมูลของคุณเองโดยใช้การล็อกหรือวิธีอื่น ๆ ที่. NET ให้คุณปลอดภัยต่อเธรด นี่เป็นตัวอย่างที่ดี: วิธีการใช้ ConcurrentHashSet ใน. Net

ข้อเสียเปรียบเพียงอย่างเดียวของโซลูชันนี้คือประเภทHashSet<T>นั้นไม่สามารถเข้าถึงได้พร้อมกันอย่างเป็นทางการแม้สำหรับการอ่าน

ฉันอ้างถึงรหัสของโพสต์ที่เชื่อมโยง (แต่เดิมเขียนโดยBen Mosher )

using System;
using System.Collections.Generic;
using System.Threading;

namespace BlahBlah.Utilities
{
    public class ConcurrentHashSet<T> : IDisposable
    {
        private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
        private readonly HashSet<T> _hashSet = new HashSet<T>();

        #region Implementation of ICollection<T> ...ish
        public bool Add(T item)
        {
            _lock.EnterWriteLock();
            try
            {
                return _hashSet.Add(item);
            }
            finally
            {
                if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            }
        }

        public void Clear()
        {
            _lock.EnterWriteLock();
            try
            {
                _hashSet.Clear();
            }
            finally
            {
                if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            }
        }

        public bool Contains(T item)
        {
            _lock.EnterReadLock();
            try
            {
                return _hashSet.Contains(item);
            }
            finally
            {
                if (_lock.IsReadLockHeld) _lock.ExitReadLock();
            }
        }

        public bool Remove(T item)
        {
            _lock.EnterWriteLock();
            try
            {
                return _hashSet.Remove(item);
            }
            finally
            {
                if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            }
        }

        public int Count
        {
            get
            {
                _lock.EnterReadLock();
                try
                {
                    return _hashSet.Count;
                }
                finally
                {
                    if (_lock.IsReadLockHeld) _lock.ExitReadLock();
                }
            }
        }
        #endregion

        #region Dispose
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
                if (_lock != null)
                    _lock.Dispose();
        }
        ~ConcurrentHashSet()
        {
            Dispose(false);
        }
        #endregion
    }
}

แก้ไข:ย้ายวิธีการล็อคทางเข้าให้พ้นtryบล็อกเนื่องจากพวกเขาสามารถโยนข้อยกเว้นและดำเนินการตามคำแนะนำที่มีอยู่ในfinallyบล็อก


8
พจนานุกรมที่มีค่าขยะเป็นรายการ
Ralf

44
@Ralf มันเป็นชุดไม่ใช่รายการตามที่ไม่มีการเรียงลำดับ
Servy

11
ตามเอกสารสั้น ๆ ของ MSDN ในหัวข้อ "คอลเลกชันและการซิงโครไนซ์ (ความปลอดภัยของเธรด)"คลาสใน System.Collections และเนมสเปซที่เกี่ยวข้องสามารถอ่านได้โดยหลายเธรดได้อย่างปลอดภัย ซึ่งหมายความว่า HashSet สามารถอ่านได้อย่างปลอดภัยโดยหลายกระทู้
Hank Schultz

7
@Oliver การอ้างอิงใช้หน่วยความจำมากขึ้นต่อรายการแม้ว่าจะเป็นการnullอ้างอิง (การอ้างอิงต้องการ 4 ไบต์ในรันไทม์ 32- บิตและ 8 ไบต์ใน 64- บิตรันไทม์) ดังนั้นการใช้byteโครงสร้างว่างเปล่าหรือที่คล้ายกันอาจลดขนาดหน่วยความจำ (หรืออาจไม่ได้หากรันไทม์จัดเรียงข้อมูลบนขอบเขตหน่วยความจำดั้งเดิมเพื่อการเข้าถึงที่รวดเร็วขึ้น)
Lucero

4
การใช้งานด้วยตนเองไม่ใช่ ConcurrentHashSet แต่เป็น ThreadSafeHashSet มีความแตกต่างใหญ่ระหว่าง 2 เหล่านั้นและนั่นคือเหตุผลที่ Micorosft ละทิ้งการทำข้อมูลให้ตรงกัน (คนทำผิด) เพื่อให้การดำเนินงาน "พร้อมกัน" เช่น GetOrAdd ฯลฯ ควรดำเนินการ (เช่นพจนานุกรม) มิฉะนั้นจะไม่สามารถมั่นใจได้ว่าจะมีการทำงานพร้อมกันโดยไม่ล็อคเพิ่มเติม แต่ถ้าคุณต้องการการล็อคเพิ่มเติมนอกชั้นเรียนทำไมคุณไม่ใช้ HashSet อย่างง่ายตั้งแต่ต้น?
George Mavritsakis

36

แทนการห่อConcurrentDictionaryหรือล็อคมากกว่าHashSetฉันสร้างจริงขึ้นอยู่กับConcurrentHashSetConcurrentDictionary

การใช้งานนี้รองรับการดำเนินงานขั้นพื้นฐานต่อรายการโดยไม่ต้องHashSetตั้งค่าการดำเนินการตามที่เหมาะสมในสถานการณ์ที่เกิดขึ้นพร้อมกัน IMO:

var concurrentHashSet = new ConcurrentHashSet<string>(
    new[]
    {
        "hamster",
        "HAMster",
        "bar",
    },
    StringComparer.OrdinalIgnoreCase);

concurrentHashSet.TryRemove("foo");

if (concurrentHashSet.Contains("BAR"))
{
    Console.WriteLine(concurrentHashSet.Count);
}

ผลลัพธ์: 2

คุณจะได้รับจาก NuGet ที่นี่และดูแหล่งที่มาบน GitHub ที่นี่


3
นี่ควรเป็นคำตอบที่ได้รับการยอมรับการติดตั้งที่ยอดเยี่ยม
smirkingman

ไม่ควรเพิ่มเปลี่ยนชื่อเป็นTryAddเพื่อที่จะสอดคล้องกับConcurrentDictionary ?
Neo

8
@ ไม่มีหมายเลข ... เพราะมันตั้งใจใช้ความหมายของHashSet <T>ที่คุณเรียกใช้เพิ่มและมันจะส่งกลับบูลีนระบุว่ารายการถูกเพิ่ม (จริง) หรือมีอยู่แล้ว (เท็จ) msdn.microsoft.com/en-us/library/bb353005(v=vs.110).aspx
G-Mac

ไม่ควรนำISet<T>อินเตอร์เฟส bo มาใช้ร่วมกับซีแมนติกส์จริง ๆHashSet<T>หรือ
Nekromancer

1
@ Nikromancer อย่างที่ฉันบอกไว้ในคำตอบฉันไม่คิดว่ามันสมเหตุสมผลที่จะให้วิธีการตั้งค่าเหล่านี้ในการใช้งานพร้อมกัน Overlapsตัวอย่างเช่นอาจจำเป็นต้องล็อคอินสแตนซ์ตลอดการรันหรือให้คำตอบที่อาจผิดไปแล้ว ตัวเลือกทั้งสองนั้นเป็น IMO ที่ไม่ดี (และสามารถเพิ่มได้จากผู้บริโภคภายนอก)
i3arnon

21

เนื่องจากไม่มีใครพูดถึงมันฉันจะเสนอวิธีการอื่นที่อาจหรืออาจไม่เหมาะสมสำหรับวัตถุประสงค์เฉพาะของคุณ:

คอลเล็กชันที่ไม่เปลี่ยนรูปของ Microsoft

จากบล็อกโพสต์โดยทีม MS ที่อยู่เบื้องหลัง:

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

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

การออกแบบนี้สร้างปัญหาใหม่แม้ว่า: คุณจะจัดการการเปลี่ยนแปลงในสถานะอย่างไรโดยไม่คัดลอกสถานะทั้งหมดในแต่ละครั้ง นี่เป็นเรื่องยุ่งยากโดยเฉพาะอย่างยิ่งเมื่อมีส่วนเกี่ยวข้อง

นี่คือสิ่งที่คอลเล็กชันที่ไม่เปลี่ยนแปลงเข้ามา

คอลเลกชันเหล่านี้รวมถึงImmutableHashSet <T>และImmutableList <T>

ประสิทธิภาพ

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

ถาม: ฉันได้ยินมาว่าคอลเลกชันที่ไม่เปลี่ยนรูปนั้นช้า สิ่งเหล่านี้ต่างกันหรือไม่? ฉันสามารถใช้พวกเขาเมื่อประสิทธิภาพหรือหน่วยความจำมีความสำคัญ?

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

กล่าวอีกนัยหนึ่งในหลายกรณีความแตกต่างจะไม่สังเกตเห็นได้ชัดเจนและคุณควรเลือกตัวเลือกที่ง่ายกว่า - ซึ่งสำหรับชุดที่เกิดขึ้นพร้อมกันนั้นจะต้องใช้ImmutableHashSet<T>เนื่องจากคุณไม่มีการใช้งานการล็อคที่มีอยู่! :-)


1
ImmutableHashSet<T>ไม่ได้ช่วยอะไรมากหากความตั้งใจของคุณคือการอัพเดทสถานะที่แชร์จากหลาย ๆ กระทู้หรือฉันขาดอะไรที่นี่?
tugberk

7
@tugberk ใช่และไม่ใช่ เนื่องจากชุดนั้นไม่เปลี่ยนรูปคุณจะต้องอัปเดตข้อมูลอ้างอิงซึ่งตัวสะสมเองไม่สามารถช่วยเหลือคุณได้ ข่าวดีก็คือคุณได้ลดปัญหาที่ซับซ้อนในการปรับปรุงโครงสร้างข้อมูลที่ใช้ร่วมกันจากหลายเธรดเป็นปัญหาที่ง่ายกว่าในการอัพเดตการอ้างอิงที่ใช้ร่วมกัน ห้องสมุดจะให้วิธีการImmutableInterlocked.Updateแก่คุณเพื่อช่วยเหลือคุณ
Søren Boisen

1
@ SørenBoisenjustอ่านเกี่ยวกับคอลเลกชันที่ไม่เปลี่ยนรูปและพยายามหาวิธีใช้เธรดอย่างปลอดภัย ImmutableInterlocked.Updateดูเหมือนว่าจะเป็นลิงค์ที่ขาดหายไป ขอบคุณ!
xneg

4

ส่วนที่ยุ่งยากเกี่ยวกับการทำISet<T>พร้อมกันก็คือวิธีการตั้งค่า (สหภาพ, จุดตัด, ความแตกต่าง) นั้นซ้ำในธรรมชาติ อย่างน้อยที่สุดคุณต้องย้ำสมาชิก n ทั้งหมดของหนึ่งในชุดที่เกี่ยวข้องในการดำเนินการในขณะที่ล็อคทั้งสองชุด

คุณสูญเสียข้อดีConcurrentDictionary<T,byte>เมื่อคุณต้องล็อคทั้งชุดในระหว่างการทำซ้ำ การดำเนินการเหล่านี้จะไม่ปลอดภัยสำหรับเธรด

ด้วยค่าใช้จ่ายที่เพิ่มขึ้นของConcurrentDictionary<T,byte>มันอาจเป็นเพียงการใช้น้ำหนักเบาHashSet<T>และล้อมรอบทุกอย่างในล็อค

หากคุณไม่ต้องการการตั้งค่าให้ใช้ConcurrentDictionary<T,byte>และใช้default(byte)เป็นค่าเมื่อคุณเพิ่มคีย์


2

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

@ เซนขอบคุณที่เริ่มใช้งาน

[DebuggerDisplay("Count = {Count}")]
[Serializable]
public class ConcurrentHashSet<T> : ICollection<T>, ISet<T>, ISerializable, IDeserializationCallback
{
    private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

    private readonly HashSet<T> _hashSet = new HashSet<T>();

    public ConcurrentHashSet()
    {
    }

    public ConcurrentHashSet(IEqualityComparer<T> comparer)
    {
        _hashSet = new HashSet<T>(comparer);
    }

    public ConcurrentHashSet(IEnumerable<T> collection)
    {
        _hashSet = new HashSet<T>(collection);
    }

    public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
    {
        _hashSet = new HashSet<T>(collection, comparer);
    }

    protected ConcurrentHashSet(SerializationInfo info, StreamingContext context)
    {
        _hashSet = new HashSet<T>();

        // not sure about this one really...
        var iSerializable = _hashSet as ISerializable;
        iSerializable.GetObjectData(info, context);
    }

    #region Dispose

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
            if (_lock != null)
                _lock.Dispose();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _hashSet.GetEnumerator();
    }

    ~ConcurrentHashSet()
    {
        Dispose(false);
    }

    public void OnDeserialization(object sender)
    {
        _hashSet.OnDeserialization(sender);
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        _hashSet.GetObjectData(info, context);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion

    public void Add(T item)
    {
        _lock.EnterWriteLock();
        try
        {
            _hashSet.Add(item);
        }
        finally
        {
            if(_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public void UnionWith(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        _lock.EnterReadLock();
        try
        {
            _hashSet.UnionWith(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            if (_lock.IsReadLockHeld) _lock.ExitReadLock();
        }
    }

    public void IntersectWith(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        _lock.EnterReadLock();
        try
        {
            _hashSet.IntersectWith(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            if (_lock.IsReadLockHeld) _lock.ExitReadLock();
        }
    }

    public void ExceptWith(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        _lock.EnterReadLock();
        try
        {
            _hashSet.ExceptWith(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            if (_lock.IsReadLockHeld) _lock.ExitReadLock();
        }
    }

    public void SymmetricExceptWith(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            _hashSet.SymmetricExceptWith(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool IsSubsetOf(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.IsSubsetOf(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool IsSupersetOf(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.IsSupersetOf(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool IsProperSupersetOf(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.IsProperSupersetOf(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool IsProperSubsetOf(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.IsProperSubsetOf(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool Overlaps(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.Overlaps(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool SetEquals(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.SetEquals(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    bool ISet<T>.Add(T item)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.Add(item);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public void Clear()
    {
        _lock.EnterWriteLock();
        try
        {
            _hashSet.Clear();
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool Contains(T item)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.Contains(item);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        _lock.EnterWriteLock();
        try
        {
            _hashSet.CopyTo(array, arrayIndex);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool Remove(T item)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.Remove(item);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public int Count
    {
        get
        {
            _lock.EnterWriteLock();
            try
            {
                return _hashSet.Count;
            }
            finally
            {
                if(_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            }

        }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }
}

ล็อคได้รับการกำจัด ... แต่สิ่งที่เกี่ยวกับแฮชเซ็ตด้านในเมื่อหน่วยความจำของมันถูกปล่อยออกมา?
David Rettenbacher

1
@Warappa ปล่อยออกมาเมื่อมีการเก็บขยะ ครั้งเดียวที่ฉันลบล้างสิ่งเหล่านี้ด้วยตนเองและล้างการแสดงตนทั้งหมดภายในคลาสคือเมื่ออาสาสมัครมีเหตุการณ์และอาจทำให้หน่วยความจำรั่ว (เช่นเมื่อคุณใช้ ObservableCollection และเหตุการณ์ที่เปลี่ยนแปลง) ฉันเปิดรับข้อเสนอแนะหากคุณสามารถเพิ่มความรู้ให้กับความเข้าใจของฉันเกี่ยวกับเรื่องนี้ ฉันใช้เวลาสองสามวันในการค้นคว้าเกี่ยวกับการเก็บขยะด้วยและฉันมักจะอยากรู้ข้อมูลใหม่ ๆ อยู่เสมอ
Dbl

@ AndreasMüllerคำตอบที่ดี แต่ฉันสงสัยว่าทำไมคุณถึงใช้ '_lock.EnterWriteLock ();' ตามด้วย '_lock.EnterReadLock ();' ในวิธีการบางอย่างเช่น 'IntersectWith' ฉันคิดว่าไม่จำเป็นต้องอ่านนี่เพราะการล็อกการเขียนจะป้องกันการอ่านใด ๆ เมื่อป้อนโดยค่าเริ่มต้น
Jalal Said

หากคุณต้องการเสมอEnterWriteLockทำไมEnterReadLockถึงมีอยู่? ไม่สามารถใช้ล็อกการอ่านสำหรับวิธีการได้Containsหรือไม่
ErikE

2
นี่ไม่ใช่ ConcurrentHashSet แต่เป็น ThreadSafeHashSet ดูความคิดเห็นของฉันใน @ZenLulz คำตอบเกี่ยวกับการใช้งานด้วยตนเอง ฉันมั่นใจว่า 99% ว่าทุกคนที่ใช้การใช้งานเหล่านั้นจะมีข้อบกพร่องร้ายแรงในแอปพลิเคชันของพวกเขา
George Mavritsakis
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.