c# تحميل - المتزامنة HashSet <T> في .NET Framework؟





كامل برنامج (5)


أنا أفضل الحلول الكاملة لذلك فعلت هذا: عقل أن يتم تنفيذ بلدي العد بطريقة مختلفة لأنني لا أرى لماذا ينبغي منع المرء لقراءة hashset أثناء محاولة حساب قيمها.

Zen ، شكرًا على البدء.

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

لدي الطبقة التالية.

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

هل هناك حل أفضل ، للذهاب مباشرة إلى الميدان وحمايته من الوصول المتزامن بواسطة خيوط متعددة؟




التنفيذ الخاص بك هو الصحيح. لا يوفر .NET Framework نوعًا متزامنًا لمقطع متزامن ، للأسف. ومع ذلك ، هناك بعض الحلول.

ConcurrentDictionary (مستحسن)

هذا الأول هو استخدام فئة ConcurrentDictionary<TKey, TValue> في مساحة الاسم System.Collections.Concurrent . في هذه الحالة ، القيمة لا معنى لها ، لذلك يمكننا استخدام byte بسيط (1 بايت في الذاكرة).

private ConcurrentDictionary<string, byte> _data;

هذا هو الخيار المستحسن لأن النوع مؤشر ترابط آمن ويوفر لك نفس الميزات من HashSet<T> باستثناء المفتاح والقيمة كائنات مختلفة.

المصدر: Social MSDN

ConcurrentBag

إذا كنت لا تمانع في الإدخالات المكررة ، يمكنك استخدام الفئة ConcurrentBag<T> في نفس مساحة الاسم للفئة السابقة.

private ConcurrentBag<string> _data;

التنفيذ الذاتي

وأخيرًا ، كما فعلت ، يمكنك تنفيذ نوع البيانات الخاص بك ، باستخدام القفل أو الطرق الأخرى التي توفرها لك .NET لتكون خيطًا آمنًا. إليك مثالاً رائعًا: كيفية تنفيذ ConcurrentHashSet في .Net

العيب الوحيد لهذا الحل هو أن النوع HashSet<T> لا HashSet<T> بشكل متزامن مع الوصول ، حتى في عمليات القراءة.

أقتبس رمز المنشور المرتبط (الذي كتبه أصلاً بن موشير ).

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

تحرير: نقل أساليب تأمين مدخل ouside كتل try ، لأنها يمكن أن تلقي استثناء وتنفيذ التعليمات الواردة في كتل في finally .




بما أن أي شخص آخر لم يذكر ذلك ، فسأقدم منهجًا بديلاً قد يكون أو لا يكون مناسبًا لغرضك الخاص:

مجموعات Microsoft غير القابلة للتغيير

من مشاركة مدونة بواسطة فريق MS خلف:

في حين أن إنشاء وتشغيل في وقت واحد هو أسهل من أي وقت مضى ، لا تزال واحدة من المشاكل الأساسية: الدولة المشتركة قابلة للتغيير. القراءة من خيوط متعددة عادة ما تكون سهلة للغاية ، ولكن بمجرد تحديث الدولة ، يصبح الأمر أصعب بكثير ، خاصة في التصاميم التي تتطلب القفل.

بديل للقفل هو استخدام الدولة غير القابلة للتغيير. من المؤكد أن بنية البيانات غير القابلة للتغيير لا يمكن تغييرها ، وبالتالي يمكن تمريرها بحرية بين خيوط مختلفة دون القلق من التدخل في أصابع شخص آخر.

هذا التصميم يخلق مشكلة جديدة على الرغم من: كيف تدير التغييرات في الحالة دون نسخ الحالة بأكملها في كل مرة؟ هذا أمر صعب خاصة عندما تشارك المجموعات.

هذا هو المكان الذي يأتي في مجموعات ثابتة.

تتضمن هذه المجموعات ImmutableHashSet<T> و ImmutableList<T> .

أداء

بما أن المجموعات غير القابلة للتغيير تستخدم هياكل البيانات الشجرية في الأسفل لتمكين المشاركة الهيكلية ، فإن خصائص أدائها تختلف عن المجموعات القابلة للتغيير. عند المقارنة مع مجموعة تأمين قابلة للتغيير ، تعتمد النتائج على التنازع على القفل وأنماط الوصول. ومع ذلك ، مأخوذ من مشاركة مدونة أخرى حول المجموعات الثابتة:

س: لقد سمعت أن المجموعات الثابتة هي بطيئة. هل هذه مختلفة؟ هل يمكنني استخدامها عندما يكون الأداء أو الذاكرة مهمين؟

ج: لقد تم ضبط هذه المجموعات الثابتة بشكل كبير للحصول على خصائص أداء تنافسية للمجموعات القابلة للتغيير في الوقت الذي توازن فيه مشاركة الذاكرة. وفي بعض الحالات ، تكون هذه العمليات بنفس سرعة المجموعات المتغيرة خوارزميًا وفي الوقت الفعلي ، وأحيانًا بشكل أسرع ، بينما تكون في حالات أخرى أكثر تعقيدًا من الناحية الخوارزمية. في كثير من الحالات ، سيكون الفرق مهملاً. بشكل عام ، يجب عليك استخدام أبسط رمز لإنجاز المهمة ثم ضبط الأداء حسب الضرورة. تساعدك المجموعات غير القابلة للتغيير على كتابة كود بسيط ، خاصة عند أخذ موضوع السلامة في الاعتبار.

وبعبارة أخرى ، في حالات كثيرة لن يكون الفرق ملحوظًا ويجب أن تذهب مع الاختيار الأبسط - والذي بالنسبة للمجموعات المتزامنة سيكون استخدام ImmutableHashSet<T> ، بما أنه لا يوجد لديك تطبيق ImmutableHashSet<T> قابلاً للتغيير! :-)




الجزء الصعب حول جعل ISet<T> متزامن هو أن الأساليب المحددة (الاتحاد ، التقاطع ، الاختلاف) متكررة في طبيعتها. على أقل تقدير لديك للتكرار على جميع أعضاء ن واحدة من مجموعات تشارك في العملية ، في حين قفل كلا المجموعتين.

تفقد مزايا ConcurrentDictionary<T,byte> عندما يكون لديك لتأمين المجموعة بأكملها أثناء التكرار. بدون قفل ، هذه العمليات ليست خيط آمنة.

وبالنظر إلى الزيادة الإضافية في ConcurrentDictionary<T,byte> ، فمن الحكمة على الأرجح استخدام الوزن الخفيف HashSet<T> وإحاطة كل شيء بالأقفال فقط.

إذا لم تكن بحاجة إلى عمليات تعيين ، فاستخدم ConcurrentDictionary<T,byte> واستخدم فقط default(byte) كقيمة عند إضافة المفاتيح.




لماذا لا تستخدم هذا فقط؟

var interopHelper = new WindowInteropHelper(System.Windows.Application.Current.MainWindow);
var activeScreen = Screen.FromHandle(interopHelper.Handle);




c# multithreading thread-safety locking mutex