تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
تزامن المسارات Thread Synchronization
#1
كاتب الموضوع : samerselo

The SyncLock Statement

خلال زمن التشغيل لا يوجد شئ يضمن لك أن يسير الكود بشكل نظامي بدون مقاطعات وتكون عملية التشغيل بدون مقاطعات عملية قاسية على نظام التشغيل وخاصة عندما يكون عبارة عن بيئة متعددة المهام وفي معظم الحالات التي ستحتاجها ستكون قانعا بالدقة ضمن البرنامج الواحد وذلك عند معالجة الكود فعلى سبيل المثال يكون كافيا لك ضمان أن مسار تنفيذ واحد ضمن التطبيق الحالي يستطيع تنفيذ قطعة معينة من الكود في وقت محدد ويمكنك تحقيق ذلك بتضمين قطعة الكود تلك ضمن كتلة SyncLock…End SyncLock والذي يحتاج إلى متغير كمحدد له محققا المتطلبات التالية:
• يجب أن يكون مشترك بين جميع المسارات ويكون في العادة متغير على مستوى الفئة وبدون الخاصية ThreadStatic
• يجب أن يكون من نوع مرجعي مثل String أو Object واستخدام أنواع القيمة ينتج عنه خطأ في الترجمة
• يجب أن لا يحتوي على القيمة Nothing وفي حال تمرير القيمة Nothing سيسبب أخطاء في زمن التنفيذ
وفيما يلي مثال عن كتلة SyncLock

كود :
' The lock object. (Any non-Nothing reference value will do.)
Private consoleLock As New Object()

Sub SynchronizationProblem_Task(ByVal obj As Object)
Dim number As Integer = CInt(obj)
' Print a lot of information to the console window.
For i As Integer = 1 To 1000
SyncLock consoleLock
' Split the output line in two pieces.
Console.Write(" ")
Console.Write(number)
End SyncLock
Next
End Sub
والكود السابق يستخدم المتغير consoleLock للتحكم بالوصول للغرض Console وهو يشكل المصدر الوحيد المشترك بين جميع المسارات في المثال ولهذا فهو المصدر الذي يجب عليك تحقيق التزامن من أجله والتطبيقات الحقيقية يمكن أن تحوي العديد من كتل SyncLock والتي يمكن أن تستخدم نفس المتغير المحلي أو عدة متغيرات مختلفة من أجل اختلاف البصمة وهنا يجب عليك استخدام متغيرا مميزا من أجل كل نوع من أنواع المصادر المشتركة التي يجب عمل التزامن من اجلها أو من أجل مجموعة التعابير التي يجب تنفيذها ضمن المسار في نفس الوقت.
وعندما تستخدم كتلة SyncLock يتضمن الكود تلقائيا كتلة Try…End Try مخفية من أجل ضمان تحرير القفل بشكل صحيح إذا تم إطلاق استثناء ومن أجل هذا لا يمكنك القفز لعبارة داخل الكتلة SyncLock. وإن كانت الكتلة SyncLock موضوعة داخل إجراء خاص بتواجد Instance لفئة ما وجميع المسارات العاملة ضمن إجراء في ذلك التواجد Instance للفئة يمكنك تمرير Me لعبارة الـ SyncLock وذلك بسبب أن هذا الغرض يحقق كل المتطلبات (يمكن الوصول إليه من جميع المسارات – وهو قيمة مرجعية – وبالتأكيد هو ليس Nothing)

كود :
Class TestClass
Sub TheTask()
SyncLock Me
' Only one thread at a time can access this code.

End SyncLock
End Sub
End Class
ملاحظة: يمكنك استخدام Me بهذه الطريقة فقط إن كنت تريد عمل التزامن على مصدر وحيد كملف محدد مثلا أو نافذة الكونسول Console Window وإن كان لديك عدة كتل تزامن التي تحمي عدة مصادر ستستخدم بشكل تلقائي عدة متغيرات كبارامترات لكتلة SyncLock. والشئ الذي له أهمية أكبر مما ذكر هو أنه يجب عليك استخدام Me كبارامتر فقط إذا كانت الفئة غير مرئية خارج المجمع الحالي عدا ذلك يمكن لتطبيق آخر استخدام نفس التواجد Instance للفئة ضمن كتلة SyncLock مختلفة وبهذا فلن يتم تنفيذ عدة كتل من الكود بدون سبب حقيقي محدد وبشكل عام لا يجب عليك استخدام غرض Object عام مرئي من مجمعات أخرى كبارامتر لكتلة SyncLock. وتجدر الملاحظة أن العديد من الأكواد التي تراها على الانترنت تستخدم العامل GetType للحصول على نوع الغرض المستخدم للقفل lock object وذلك لحماية الطريقة الساكنة.
عندما تستخدم عبارات SyncLock معششة للقيام بالتزامن لأغراض مختلفة من الضروري استخدام تسلسل تعشيش متطابق أينما احتجت له في تطبيقك فالتحري عن الأقفال بالتسلسل المطابق ذاته يجنبك الوصول إلى حالة الأقفال الميتة خلال العديد من أجزاء التطبيق وهذه القاعدة تنطبق أيضا عندما تقوم دالة تحتوي على SyncLock باستدعاء دالة أخرى تحتوي على SyncLock

كود :
' Always use this sequence when locking objLock1 and objLock2.
SyncLock objLock1
SyncLock objLock2

End SyncLock
End SyncLock
اعتبارات الأداء والتواجد الكسول Performance Considerations and Lazy Instantiation
تضمين جميع الأكواد التي تستخدم متغيرات مشتركة ضمن كتلة SyncLock يؤدي إلى إبطاء تطبيقك كثيرا أو تخفيض أداؤه بشكل ملحوظ وبشكل خاص عندما يتم تشغيله على حاسب متعدد المعالجات فإن استطعت تجنب استخدام كتلة SyncLock بدون تعريض تكامل البيانات للخطر يجب عليك القيام به قطعيا فمثلا تخيل أنك تستخدم نمط وحيد بتواجد كسول lazy instantiation في بيئة متعددة المسارات

كود :
Public Class Singleton
Private Shared m_Instance As Singleton
Private Shared sharedLock As New Object()

Public Shared ReadOnly Property Instance() As Singleton
Get
SyncLock sharedLock
If m_Instance Is Nothing Then m_Instance = New Singleton
Return m_Instance
End SyncLock
End Get
End Property
End Class
تكمن المشكلة في الكود السابق أن معظم الوصولات للخاصية لا يحتاج إلى تزامن وذلك لأن المتغير الخاص m_Instance يتم تعيينه مرة واحدة في المرة الأولى التي يتم فيها قراءة الخاصية وفي ما يلي طريقة أفضل لتحقيق التصرف المطلوب

كود :
Class Singleton
Private Shared m_Instance As Singleton
Private Shared sharedLock As New Object

Public Shared ReadOnly Property Instance() As Singleton
Get
If m_Instance Is Nothing Then
SyncLock sharedLock
If m_Instance Is Nothing Then m_Instance = New Singleton()
End SyncLock
End If
Return m_Instance
End Get
End Property
End Class
الأغراض المتزامنة Synchronized Objects
مشكلة أخرى متعلقة بالمسارات في الدوت نيت هي أن ليس جميع أغراض الدوت نيت .NET object قابلة للمشاركة بأمان عبر المسارات not all .NET objects are thread-safeفعندما تقوم بكتابة تطبيق متعدد المسارات يجب عليك التأكد دوما من الوثائق للتأكد من أن الأغراض والطرائق التي تستخدمها آمنة للاستخدام عبر المسارات فعلى سبيل المثال جميع الطرق الساكنة للفئات Regex و Match أمنة عبر المسارات ولكن الطرق الغير ساكنة غير آمنة فيجب عدم استخدامها ضمن مسار مختلف وكذلك بعض أغراض الدوت نيت مثل Windows Forms objects and controls لها العديد من الحدود التي تجعل فقط المسار الذي أنشأها يمكنه استدعاء طرقها وخصائصها

أنواع دوت نيت المتزامنة Synchronized .NET Types
العديد من الأغراض الغير آمنة عبر المسارات بطبيعتها مثل ArrayList و Hashtable و Queue و SortedList و Stack و TextReader و TextWriter و التعابير النظامية تقدم طريقة ساكنة قابلة للتزامن تعيد غرض أمن للمسارات thread-safe object مكافئ للذي تم تمريره كما أن معظمها يعرض الخاصية IsSynchronized التي تعيد True عندما تتعامل مع نسخة آمنة عبر المسارات

كود :
' Create an ArrayList object, and add some values to it.
Dim al As New ArrayList()
al.Add(1): al.Add(2): al.Add(3)
' Create a synchronized, thread-safe version of this ArrayList.
Dim syncAl As ArrayList = ArrayList.Synchronized(al)
' Prove that the new object is thread-safe.
Console.WriteLine(al.IsSynchronized) ' => False
Console.WriteLine(syncAl.IsSynchronized) ' => True
' You can now share the syncAl object among different threads
تذكر دائما أن التعامل مع هذه النسخة المتزامنة يكون أبطأ من النسخة الغير متزامنة وذلك بسبب أن كل طريقة تمر عبر سلسلة من الفحوصات الداخلية وفي معظم الحالات يمكنك كتابة كود فعال أكثر إذا استخدمت المصفوفات والمجموعات العادية regular arrays and collections وقمت بمزامنة عناصرها باستخدام كتلة SyncLock العادية

The Synchronization Attribute
استخدام الخاصية System.Runtime.Remoting.Contexts.Synchronization هي أبسط طريقة لتحقيق الوصول المتزامن للغرض Object بأكمله وبذلك يستطيع مسار واحد فقط الوصول إلى حقوله وطرائقه وبذلك أي مسار يستطيع استخدام الفئة ولكن مسار واحد فقط يستطيع تنفيذ أحد طرائقه إذا كانت الطريقة تنفذ كودا ضمن الفئة Class وأي مسار يحاول استخدام هذه الفئة عليه الانتظار وبكلمات أخرى وكأن هناك كتل SyncLock تغلف كافة طرائق الفئة مستخدمة نفس متغير الإقفال. والكود التالي يبين كيف يمكنك مزامنة فئة باستخدام الخاصية Synchronization attribute لاحظ أيضا أن الفئة يجب أن يتم وراثتها من ContextBoundObject ليتم تعليمها كـ context-bound object

كود :
System.Runtime.Remoting.Contexts.Synchronization()> _
Class Display
Inherits ContextBoundObject

End Class
و خاصية التزامن Synchronization attribute تضمن الوصول المتزامن لجميع الحقول والخصائص والطرق ولكنها لا توفر التزامن للأعضاء الساكنين static members وهي تأخذ بارامترا اختياريا يمكن أن تكون قيمته True أو False أو أحد الثوابت التي توفرها الفئة SynchronizationAttribute والتي يمكنك الاطلاع عليها من مكتبة MSDN
}}}
تم الشكر بواسطة:
#2

The MethodImpl Attribute

في معظم الحالات مزامنة فئة كاملة ستقتل التطبيق وحماية بعض الطرائق في تلك الفئة يكون كافيا في معظم الحالات حيث يمكنك تطبيق هذا بتغليف كود الطريقة بكتلة SyncLock أو يمكنك استخدام تقنية أبسط مبنية على الصفة System.Runtime.CompilerServices.MethodImpl

كود :
Class MethodImplDemoClass
' This method can be executed by one thread at a time.
<MethodImpl(MethodImplOptions.Synchronized)> _
Sub SynchronizedMethod()

End Sub
End Class
فتطبيق الصفة MethodImpl على عدة طرائق في الفئة يؤدي نفس الغرض من تغليف كامل تلك الطرائق بكتلة SyncLock والتي تستخدم Me كمتغير إقفال وبكلمات أخرى أي مسار يستدعي طريقة معلمة بالخاصية MethodImpl سوف يمنع أي مسار آخر من استدعاء الطريقة المعلمة بالخاصية MethodImpl كما يمكنك استخدام هذه الخاصية على الطرائق الساكنة ويكون متغير الغرض الذي يستخدم ضمنيا لقفل الطرائق الساكنة مختلف عن متغير الغرض المستخدم لقفل الطرائق الأخرى للفئة instance methods وبهذا فالمسار الذي يستدعي طريقة ساكنة معلمة بالصفة MethodImpl لا يمنع مسار آخر من استدعاء الطرائق الغير ساكنة instance methods والمعلمة بنفس الصفة

عمليات القراءة والكتابة المتغيرة Volatile Read and Write Operations
عندما تتم مشاركة متغير عبر عدة مسارات والتطبيق يعمل على حاسب متعدد المعالجات يجب عليك وضع احتمال حدوث أخطاء إضافية في الحسبان وتكمن المشكلة في النظام متعدد المعالجات في أن لكل معالج الكاش الخاص به ولهذا فإذا قمت بالكتابة على حقل في فئة على مسار سيتم كتابة القيمة الجديدة في الكاش المرتبط مع المعالج الحالي ولا يتم نشرها مباشرة إلى الكاش الخاص ببقية المعالجات بحيث يمكنهم جميعا رؤية القيمة الجديدة. كما تحدث مشكلة مشابهة في الأنظمة ذات المعالج 64 بت الذي يمكنه إعادة ترتيب تنفيذ كتل عبارات الكود متضمنا عمليات القراءة والكتابة في الذاكرة وعملية إعادة الترتيب لم يكن لها تأثير ظاهر حتى الآن من أجل مسار واحد يستخدم جزءا معينا من الذاكرة ولكن ربما سيسبب ذلك مشكلة عندما يتم الوصول إلى نفس الجزء من الذاكرة بواسطة عدة مسارات. وتوفر الفريموورك حلان لهذه المشكلة وهما وزج من الطرائق VolatileRead و VolatileWrite والطريقة MemoryBarrier ويوفرها جميعا النوع Thread
تمكنك الطريقة VolatileWrite من كتابة متغير والتأكد من أن القيمة الجديدة يتم كتابتها آليا في الذاكرة المشتركة بين جميع المعالجات ولا تبقى في المسجل الخاص بالمعالج حيث تكون مخفية عن بقية المسارات وبالمثل تمكنك الطريقة VolatileRead من قراءة المتغير بطريقة آمنة لأنها تجبر النظام على تفريغ جميع ذواكر الكاش الموجودة قبل تنفيذ العملية وكلا الطريقتان محملتان تحميلا زائدا Overloaded بحيث تأخذ متغيرات رقمية أو غرضية Object وبالمرجع كما في قطعة الكود التالية

كود :
Class TestClass
Private Shared sharedValue As Integer

Function IncrementValue() As Integer
Dim value As Integer = Thread.VolatileRead(sharedValue)
value += 1
Thread.VolatileWrite(sharedValue, value)
Return value
End Function
End Class
والطريقتان المذكورتان تعملان بشكل جيد عندما نتعامل مع المتغيرات الرقمية أو الغرضية Object ولكن لا يمكن استخدامهما من أجل أنواع أخرى من المتغيرات لأنه لا يمكنك استخدام نسخة الدالة التي تأخذ متغير من النوع Object بسبب عدم إمكانية الاعتماد على عملية التحويل عندما يكون المتغير ممررا بالمرجع مما يقودنا إلى الطريقة MemoryBarrier التي تقوم بتفريغ محتويات جميع ذواكر الكاش الخاصة بالمعالجات إلى الذاكرة الرئيسية وبهذا تضمن لك أن جميع المتغيرات تحتوي أحدث نسخة من البيانات التي تمت كتابتها إليهم فمثلا يضمن الكود التالي أن الفئة Singleton تعمل جيدا حتى على نظام متعدد المعالجات

كود :
Class Singleton
Private Shared m_Instance As Singleton
Private Shared sharedLock As New Object()

Public Shared ReadOnly Property Instance() As Singleton
Get
If m_Instance Is Nothing Then
SyncLock sharedLock
If m_Instance Is Nothing Then
Dim tempInstance As Singleton = New Singleton()
' Ensure that writes related to instantiation are flushed.
Thread.MemoryBarrier()
m_Instance = tempInstance
End If
End SyncLock
End If
Return m_Instance
End Get
End Property
End Class
ويجب عليك استدعاء الطريقة MemoryBarrier مباشرة قبل أن يتم نشر القيمة الجديدة إلى بقية المسارات وفي المثال السابق يتم التأكد من اكتمال وضع القيمة في المتغير tempInstance قبل أن توضع في المتغير الذي ستتم مشاركته عبر المسارات

The Monitor Type
توفر كتلة SyncLock طريقة سهلة لاستخدام طريقة تتعامل مع مسائل التزامن ولكنها تكون غير ملائمة في العديد من الحالات فمثلا لا يمكن للمسار اختبار كود في كتلة SyncLock وتجنب منعه من ذلك إذا كان مسار آخر ينفذ كتلة SyncLock مرتبطة مع نفس الغرض Object وكتل SyncLock معرفة داخليا بواسطة Monitor objects التي يمكن استخدامها مباشرة للحصول على مرونة أكثر ويتم ذلك على حساب زيادة التعقيد في الكود. ولا يمكنك استخدام Monitor object وحيد وفي الحقيقة جميع طرق Monitor type التي سيتم عرضها هي طرائق ساكنة وتعتبر Enter هي الطريقة الأهم وهي تأخذ بارامتر من النوع Object الذي يعمل كالبارامتر الممرر لكتلة SyncLock وتكون له نفس الشروط من كونه من نوع مرجعي ومشترك ولا يمكن أن يحمل القيمة Nothing وإن لم تمتلك المسارات الأخرى قفلا على هذا الغرض فيقوم المسار الحالي بطلب ذلك القفل ويضبط قيمة العداد إلى 1 وإن امتلك مسار آخر القفل يجب على المسار الطالب انتظار أن يقوم المسار الآخر بتحرير القفل حتى يصبح متوفرا وإن كان المسار الطالب يمتلك القفل أساسا يؤدي كل استدعاء للطريقة Monitor.Enter إلى زيادة قيمة العداد. وتأخذ الطريقة Monitor.Exit غرض القفل lock object كبارامتر لها وتنقص قيمة العداد وعندما تصل قيمة العداد للصفر يتم تحرير القفل ممكنا بقية المسارات من الحصول عليه ويجب أن يتم الموازنة بين استدعاء الطريقة Monitor.Enter والطريقة Monitor.Exit أو لن يتم تحرير القفل أبدا

كود :
' A non-Nothing module-level object variable
Dim objLock As New Object()

Try
' Attempt to enter the protected section;
' wait if the lock is currently owned by another thread.
Monitor.Enter(objLock)
' Do something here.

Finally
' Release the lock.
Monitor.Exit(objLock)
End Try
إذا كان هناك احتمال في أن تطلق العبارات الموجودة بين الطريقتان Enter و Exit استثناء يجب عليك عندها وضع كامل الكود ضمن كتلة Try…End Try لأنه من الضروري أن تقوم بتحرير القفل دوما وإن طلب مسار طريقة على مسار آخر تنتظر داخل الطريقة Monitor.Enter سوف يستقبل ذلك المسار استثناء ThreadInterruptedException الذي يعتبر سببا إضافيا لاستخدام كتلة Try…End Try والطريقتان Enter و Exit الخاصتين بـ Monitor Object يسمحان لك باستبدال كتلة SyncLock ولكنهما لا يقدمان لك أية فوائد إضافية وسوف ترى المرونة الزائدة للفئة Monitor عندما تطبق الطريقة TryEnter وهي مشابهة للطريقة Enter ولكنها تخرج وتعيد False إذا كان لا يمكن الحصول على القفل خلال فترة زمنية محددة فمثلا يمكنك محاولة الحصول على Monitor خلال 10 ميللي ثانية ثم التخلي عن ذلك دون أن توقف المسار الحالي مدة غير محددة ويقوم الكود التالي بإعادة كتابة المثال السابق المعتمد على SyncLock مستخدما Monitor object ويظهر لك المحاولات الفاشلة للحصول على القفل

كود :
Try
Do Until Monitor.TryEnter(consoleLock, 10)
Debug.WriteLine("Thread " + Thread.CurrentThread.Name + _
" failed to acquire the lock")
Loop
' Split the output line in pieces.
Console.Write(" ")
Console.Write(Thread.CurrentThread.Name)
Finally
' Release the lock.
Monitor.Exit(consoleLock)
End Try
The Mutex Type
يوفر النوع Mutex مبدأ آخر للتزامن حيث أن الـ Mutex هو Windows kernel object يمكن امتلاكه من قبل مسار واحد فقط في الوقت نفسه ويكون في حالة إشارة a signaled state عندما لا يمتلكه أي مسار. ويطلب المسار ملكية الـ Mutex باستخدام الطريقة الساكنة Mutex.WaitOne والتي لا تعود إلا بعد أن يتم تحقيق الملكية ويتم تحريرها باستخدام الطريقة الساكنة Mutex.ReleaseMutex والمسار الذي يطلب ملكية Mutex object المملوك من قبله سلفا لا يمنع نفسه من الحصول على الملكية فيجب عليك في هذه الحالة استدعاء ReleaseMutex بعدد مساوي من المرات وهذا مثال عن كيفية تعريف قسم متزامن باستخدام Mutex object

كود :
' This Mutex object must be accessible to all threads.
Dim m As New Mutex()

Sub WaitOneExample()
m.WaitOne()
' Enter the synchronized section.

' Exit the synchronized section.
m.ReleaseMutex()
End Sub
وفي التطبيقات الحقيقية عليك استخدام كتلة Try لحماية كودك من الأخطاء ووضع استدعاء ReleaseMutex في قسم Finally وإن قمت بتمرير بارامتر اختياري للطريقة WaitOne كزمن انتهاء فستعيد التحكم للمسار عندما يتم تحقيق الملكية بنجاح أو عندما ينتهي الوقت المحدد ويمكن معرفة الفرق بين النتيجتين باختبار القيمة المعادة حيث أن True تعني تحقيق الملكية و False تعني انتهاء الوقت

كود :
' Attempt to enter the synchronized section, but give up after 0.1 seconds.
If m.WaitOne(100, False) Then

' Enter the synchronized section.

' Exit the synchronized section, and release the mutex.
m.ReleaseMutex()
End If
عند استخدام هذه الطريقة يوفر النوع Mutex آلية مكافئة للطريقة Monitor.TryEnter بدون تقديم أية خصائص إضافية ويمكنك رؤية المرونة الإضافية للنوع Mutex عندما ترى الطريقتين الساكنتين WaitAny و WaitAll الخاصتين به والطريقة WaitAny تأخذ مصفوفة من Mutex objects وتعود عندما تحقق ملكية واحد من Mutex objects من تلك القائمة وفي هذه الحالة يصبح الـ Mutex في حالة إشارة أو عندما ينتهي الوقت المحدد بالبارامتر الاختياري والقيمة المعادة تكون عبارة عن مصفوفة من Mutex objects التي أصبحت في حالة إشارة أو قيمة خاصة هي 258 عندما ينتهي الوقت المحدد. وتستخدم مصفوفة من Mutex objects عندما يكون لدينا عدد محدود من الموارد ونريد أن نربط كل واحد منها بمسار حالما يصبح ذلك المصدر متوفرا وفي هذه الحالة يصبح الـ Mutex objects الذي في حالة إشارة يعني أن المصدر الموافق متوفر عندئذ يمكنك استخدام الطريقة Mutex.WaitAny لمنع المسار الحالي حتى يصبح واحدا من الـ Mutex objects في حالة إشارة و النوع Mutex يرث الطريقة WaitAny من WaitHandle الخاصة بفئته الأب وهذا هيكل لتطبيق يستخدم هذه التقنية

كود :
' An array of three Mutex objects
Dim mutexes() As Mutex = {New Mutex(), New Mutex(), New Mutex()}

Sub WaitAnyExample()
' Wait until a resource becomes available.
' (Returns the index of the available resource.)
Dim mutexNdx As Integer = Mutex.WaitAny(mutexes)
' Enter the synchronized section.
' (This code should use only the resource corresponding to mutexNdx.)

' Exit the synchronized section, and release the resource.
mutexes(mutexNdx).ReleaseMutex()
End Sub
والطريقة الساكنة WaitAll أيضا موروثة من WaitHandle الخاصة بالفئة الأب حيث تأخذ مصفوفة من Mutex objects وتعيد التحكم للتطبيق فقط عندما يصبح جميعهم في حالة إشارة وهي مفيدة بشكل خاص عندما لا يمكنك المتابعة إلا عندما تكون جميع المسارات الباقية قد أنهت عملها

كود :
' Wait until all resources have been released.
Mutex.WaitAll(mutexes)
وهناك مشكلة صغيرة متعلقة بالطريقة WaitAll هي أنه لا يمكن استدعاؤها من المسار الرئيسي في تطبيق مسار الغرفة الوحيدة Single Thread Apartment (STA) application مثل تطبيق الكونسول Console application أو تطبيق نماذج ويندوز Windows Forms application ففي المسار الرئيسي لتطبيق STA يجب عليك التوقف حتى يتم تحرير مجموعة من الـ Mutex عندها يجب عليك استخدام WaitAll من مسار منفصل ثم استخدام الطريقة Thread.Join على ذلك المسار لإيقاف المسار الرئيسي حتى تعود الطريقة WaitAll وفي فيجول بايزيك 2005 والنسخة 2 من الفريموورك يوجد الطريقة الساكنة الجديدة SignalAndWait تمكنك من وضع Mutex object في حالة إشارة وانتظار Mutex object آخر

كود :
' Signal the first mutex and wait for the second mutex to become signaled.
Mutex.SignalAndWait(mutexes(0), mutexes(1))
وخلافا لجميع أغراض التزامن التي تم ذكرها حتى الآن يمكن لـ Mutex objects أن يرتبط باسم الأمر الذي يعد من أهم المزايا لهذه الأغراض فأغراض Mutex objects التي تمتلك نفس الاسم يمكن مشاركتها عبر العمليات ويمكنك إنشاء تواجد Instance لها كما يلي

كود :
Dim m As New Mutex(False, "mutexname")
وإن كان الاسم موجودا سابقا في النظام يحصل المستدعي على مرجع له وإلا سيتم إنشاء Mutex object جديد بحيث تمكنك هذه الآلية من مشاركة Mutex objects عبر عدة تطبيقات مختلفة وبهذا تتمكن هذه التطبيقات من مزامنة عمليات الوصول للمصادر المختلفة وقد تم إضافة باني جديد في الفريموورك 2 وفيجول بايزيك 2005 يمكنك من اختبار إذا كان قد تم منح المسار المستدعي ملكية الـ Mutex

كود :
Dim ownership As Boolean
Dim m As New Mutex(True, "mutexname", ownership)
If ownership Then
' This thread owns the mutex.

End If
من الاستخدامات الشائعة لـ named mutexes هو تحديد فيما إذا كان التطبيق العامل هو الأول أو الوحيد الذي تم تحميله وإن لم تكن هذه الحالة يمكن للتطبيق الخروج مباشرة أو الانتظار حتى تنتهي النسخة الأخرى من مهامها كما في المثال

كود :
Sub Main()
Dim ownership As Boolean
Dim m As New Mutex(True, "DemoMutex", ownership)
If ownership Then
Console.WriteLine("This app got the ownership of Mutex named DemoMutex")
Console.WriteLine("Press ENTER to run another instance of this app")
Console.ReadLine()
Process.Start(Assembly.GetExecutingAssembly().GetName().CodeBase)
Else
Console.WriteLine("This app is waiting to get ownership of Mutex named DemoMutex")
m.WaitOne()
End If
' Perform the task here.

Console.WriteLine("Press ENTER to release ownership of the mutex")
Console.ReadLine()
m.ReleaseMutex()
End Sub
والطريقة الساكنة OpenExisting جديدة أيضا في الفريموورك 2 وتقدم طريقة أخرى لفتح Mutex على مستوى النظام named system-wide Mutex object وبعكس باني الـ Mutex تمكنك هذه الطريقة من تحديد درجة التحكم التي تريدها على الـ Mutex

كود :
Try
' Request a mutex with the right to wait for it and to release it.
Dim rights As MutexRights = MutexRights.Synchronize Or MutexRights.Modify
Dim m As Mutex = Mutex.OpenExisting("mutexname", rights)
' Use the mutex here.

Catch ex As WaitHandleCannotBeOpenedException
' The specified object doesn't exist.
Catch ex As UnauthorizedAccessException
' The specified object exists, but current user doesn't have the
' necessary access rights.
Catch ex As IOException
' A Win32 error has occurred.
End Try
وفي فيجول بايزيك 2005 والفريموورك 2 تظهر الميزة الجديدة الأهم في النوع Mutex وهي إمكانية الوصول لقوائم التحكم بالوصول access control lists (ACLs) في النموذج عبر الغرض System.Security.AccessControl.MutexSecurity object حيث يمكنك تحديد ACL عندما تنشئ غرض Mutex جديد مستخدما الطريقة GetAccessControl للحصول على غرض MutexSecurity المرتبط بـ Mutex محدد وتطبيق ACL جديد باستخدام الطريقة SetAccessControl

كود :
Dim ownership As Boolean
Dim m As New Mutex(True, "mutexname", ownership)
If Not ownership Then
' Determine who is the owner of the mutex.
Dim mutexSec As MutexSecurity = m.GetAccessControl()
Dim account As NTAccount = DirectCast(mutexSec.GetOwner(GetType(NTAccount)), _
NTAccount)
Console.WriteLine("Mutex is owned by {0}", account)
End If
}}}
تم الشكر بواسطة:
#3

The Semaphore Type

تقدم الفريموورك 2 و فيجول بايزيك دوت نيت نوعا جديدا وهو Semaphore type الذي يرتكز على Win32 semaphore object وخلافا لبقية أغراض المسارات الموجودة في المكتبة mscorlib فهذا النوع تم تعريفه في المكتبة system.dll وهو يستخدم عندما تريد تحديد حد أقصى (عدد N) من المسارات التي يمكن تنفيذها في جزء معين من الكود أو للوصول إلى مصدر معين ويمتلك عددا ابتدائيا وعددا أقصى ويجب عليك تمرير هذه القيم لبانيه

كود :
' A semaphore that has an initial count of 1 and a maximum count of 2.
Dim sem As New Semaphore(1, 2)
يحاول المسار أخذ ملكية الـ semaphore باستدعاء الطريقة WaitOne وإن كان العدد الحالي أكبر من الصفر يتم إنقاصه وتعود الطريقة مباشرة وإلا تنتظر حتى يحرر مسار آخر semaphore أو إنقضاء الوقت المحدد بالبارامتر الاختياري ويحرر المسار semaphore باستدعاء الطريقة Release مما يزيد العدد بمقدار 1 أو بقيمة محددة ويعيد قيمة العدد السابق

كود :
Dim sem As New Semaphore(2, 2)
' Next statement brings count from 2 to 1.
sem.WaitOne()

' Next statement brings count from 1 to 2.
sem.Release()
' Next statement attempts to bring count from 2 to 3, but
' throws a SemaphoreFullException.
sem.Release()
وبشكل أساسي ستستخدم الغرض Semaphore كما يلي

' Initial count is initially equal to max count.
Dim sem2 As New Semaphore(2, 2)

Sub Semaphore_Example()
' Wait until a resource becomes available.
sem2.WaitOne()
' Enter the synchronized section.

' Exit the synchronized section, and release the resource.
sem2.Release()
End Sub
تذكر دوما استخدام الكتلة Try…Finally للتأكد من أن الـ semaphore قد تم تحريره حتى لو حدث استثناء ما وتماما كالـ mutexes يمكن للـ semaphores امتلاك اسم ومشاركته عبر العمليات وعندما تحاول إنشاء غرض semaphores موجود سابقا يتم تجاهل العدد والحد الأقصى

كود :
Dim ownership As Boolean
Dim sem3 As New Semaphore(2, 2, "semaphoreName", ownership)
If ownership Then
' Current thread has the ownership of the semaphore.

End If
ويدعم الغرض Semaphore أيضا الـ ACLs التي يمكن تمريرها للباني حيث تتم القراءة بواسطة الطريقة GetAccessControl والتعديل بواسطة الطريقة SetAccessControl ومن الضروري أن تلاحظ أن النوع Mutex والنوع Semaphore تتم وراثتهما من الفئة الأساسية WaitHandle لذا يمكن تمريرهما كبارامترات للطرائق الساكنة WaitAny و WaitAll و SignalAndWaitللنوع WaitHandle مما يمكنك من مزامنة المصادر بسهولة والتي تكون محمية بواسطة أيا من هذه الأغراض كما في الكود

كود :
' Wait until two mutexes, two semaphores, and one event object become signaled.
Dim waitHandles() As WaitHandle = {mutex1, mutex2, sem1, sem2, event1}
WaitHandle.WaitAll(waitHandles)
The ReaderWriterLock Type
العديد من المصادر في العالم الحقيقي يمكن إما القراءة منها أو الكتابة إليها وهي تدعم في الغالب إما مجموعة قراءات متعددة أو عملية كتابة وحيدة يتم تنفيذها في لحظة معينة فمثلا يمكن لعدة عملاء القراءة من ملف بيانات أو جدول في قاعدة بيانات ولكن إن تمت الكتابة للملف أو الجدول فلا يمكن حدوث أي عمليات قراءة أو كتابة على ذلك المصدر حيث يمكنك تعريف قفلا لكتابة واحدة أو عدة قراءات بدلالة الغرض ReaderWriterLock واستخدام هذا الغرض يعتبر رؤية إلى الأمام فكل المسارات التي تريد استخدام مصدر معين يجب عليها استخدام نفس الغرض ReaderWriterLock وقبل محاولة القيام بأي عملية على ذلك المصدر يجب على المسار استدعاء إما الطريقة AcquireReaderLock أو الطريقة AcquireWriterLock وذلك اعتمادا على العملية التي يتم تنفيذها وهذه الطرائق تقوم بإيقاف المسار الحالي حتى يتم الحصول على ذلك المصدر وأخيرا على المسار استدعاء الطريقة ReleaseReaderLock أو الطريقة ReleaseWriterLock عندما تنهي عملية القراءة أو الكتابة على ذلك المصدر والمثال التالي يقوم بإنشاء 10 مسارات تقوم بعملية قراءة أو كتابة على مصدر مشترك

كود :
Dim rwl As New ReaderWriterLock()
Dim rnd As New Random()

Sub TestReaderWriterLock()
For i As Integer = 0 To 9
Dim t As New Thread(AddressOf ReaderWriterLock_Task)
t.Start(i)
Next

End Sub

Sub ReaderWriterLock_Task(ByVal obj As Object)
Dim n As Integer = CInt(obj)
' Perform 10 read or write operations. (Reads are more frequent.)
For i As Integer = 1 To 10
If rnd.NextDouble < 0.8 Then
' Attempt a read operation.
rwl.AcquireReaderLock(Timeout.Infinite)
Console.WriteLine("Thread #{0} is reading", n)
Thread.Sleep(300)
Console.WriteLine("Thread #{0} completed the read operation", n)
rwl.ReleaseReaderLock()
Else

' Attempt a write operation.
rwl.AcquireWriterLock(Timeout.Infinite)
Console.WriteLine("Thread #{0} is writing", n)
Thread.Sleep(300)
Console.WriteLine("Thread #{0} completed the write operation", n)
rwl.ReleaseWriterLock()
End If
Next
End Sub
وعندما تشغل هذا الكود سترى أن عدة مسارات يمكنها القراءة بنفس الوقت والمسار الذي يقوم بالكتابة يوقف جميع المسارات الأخرى ويمكن للطريقتان AcquireReaderLock و AcquireWriterLock أخذ بارامتر عبارة عن زمن انتهاء timeout وذلك بقيمة من النوع TimeSpan أو بعدد من الميللي ثانية ويمكنك اختبار فيما إذا تم الحصول على القفل بنجاح باستخدام الخصائص IsReaderLockHeld و IsWriterLockHeld القابلة للقراءة فقط إذا مررت قيمة غير Timeout.Infinite

كود :
' Attempt to acquire a reader lock for no longer than 1 second.
rwl.AcquireWriterLock(1000)
If rwl.IsWriterLockHeld Then
' The thread has a writer lock on the resource.

End If
والمسار الذي يمتلك قفل القراءة يمكنه الترقية إلى قفل للكتابة باستدعاء الطريقة UpgradeToWriterLock والعودة ثانية لوضع القراءة باستخدام الطريقة Downgrade-FromWriterLock والشئ الرائع بخصوص الأغراض ReaderWriterLock هي أنها أغراض خفيفة بحيث يمكن استخدامها عددا كبيرا من المرات دون أن تؤثر على الأداء بشكل ملحوظ وبما أن الطرائق AcquireReaderLock و AcquireWriterLock تأخذان وقت انتهاء فالتطبيق المصمم بشكل جيد يجب أن لا يعاني من أقفال ميتة ومع ذلك يمكن حصول حالة قفل ميت عندما يكون مساران ينتظران مصدرا محجوزا من قبل مسار لا يقوم بتحريره حتى انتهاء العملية الجارية

The Interlocked Type
يزودنا النوع Interlocked بطريقة للقيام بعمليات دقيقة لزيادة أو إنقاص قيمة متغير مشترك وهذه الفئة تعرض فقط طرائق ساكنة (لا نحتسب هنا ما تمت وراثته من Object) انظر إلى الكود التالي

كود :
' Increment and Decrement methods work with 32-bit and 64-bit integers.
Dim lockCounter As Integer

' Increment the counter and execute some code if its previous value was zero.
If Interlocked.Increment(lockCounter) = 1 Then

End If
' Decrement the shared counter.
Interlocked.Decrement(lockCounter)
والطريقة ADD جديدة في الفريموورك 2 وهي تمكنك من زيادة أعداد حقيقية Integer من عيار 32 أو 64 بت بقيمة محددة

كود :
If Interlocked.Add(lockCounter, 2) <= 10 Then …
وتوفر الفئة Interlocked طريقتان ساكنتان أخريان الطريقة Exchange التي تمكنك من تحديد قيمة من اختيارك إلى متغيرات من النوع Integer أو Long أو Single أو Double أو IntPtr أو Objectوتعيد القيمة السابقة وبما أن لها نسخة محملة زائدا تأخذ بارامترا من النوع Object لهذا يمكنك أن تجعلها تعمل لأي نوع مرجعي كالنوع String كما في المثال

كود :
Dim s1 As String = "123"
Dim s2 As String = Interlocked.Exchange(s1, "abc")
Console.WriteLine("s1={0}, s2={1}", s1, s2)
والطريقة CompareExchange تعمل بأسلوب مشابهة ولكنها تقوم بالتبديل فقط إذا كان موقع الذاكرة مساوي لقيمة محددة يتم تمريرها لها

The ManualResetEvent, AutoResetEvent, and EventWaitHandle Types

هذه الفئات الثلاثة تعمل بشكل متشابه ManualResetEvent و AutoResetEvent و EventWaitHandle والفئة الأخيرة هي الفئة الأب للفئتان الأولان وقد تمت إضافتها في الفريموورك 2 على الرغم من أن ManualResetEvent و AutoResetEvent لم يتم إهمالهما بعد وأثناء العمل يمكنك استبدالهما بالفئة الجديدة EventWaitHandle التي تعطيك مزيدا من المرونة عند التعامل. والنوعان ManualResetEvent و AutoResetEventمفيدان بشكل خاص عندما تريد إيقاف مسار أو أكثر بشكل مؤقت حتى يخبرنا مسار آخر بأنه لا مانع من المتابعة وتستخدمهما لإيقاظ مسار مثل إجراء معالجة الحدث في مسار متوقف ولكن لا تنخدع بوجود Event في أسمائهما فلا يمكنك استخدام إجراءات معالجة الحدث التقليدية مع هذه الأغراض. وكائن من أحد هذين النوعين يمكن أن يكون في حالة إشارة أو عدم إشارة Signale/UnSignaled وهذه القيمة لا تملك أي معنى خاص بحيث يمكنك اعتبارها كحالة تشغيل/إيقاف حيث ستمرر الحالة الابتدائية للباني وأي مسار يستطيع الوصول لذلك الغرض يمكنه ضبط تلك الحالة إلى Signaled باستخدام الطريقة Set أو يستخدم الطريقة Reset لإعادة الحالة إلى UnSignaled ويمكن للمسارات الأخرى استخدام الطريقة WaitOne للانتظار حتى تصبح في حالة إشارة Signaled أو حتى انتهاء فترة الانتظار

كود :
' Create an auto reset event object in nonsignaled state.
Dim are As New AutoResetEvent(False)
' Create a manual reset event object in signaled state.
Dim mre As New ManualResetEvent(True)
والاختلاف الوحيد بين الغرضان ManualResetEvent و AutoResetEvent هو أن الأخير يعيد ضبط نفسه آليا (يصبح في حالة عدم إشارة Unsignaled) وذلك مباشرة بعد أن يتم صد المسار عندما تبدأ الطريقة WaitOne ويوقظ الغرض AutoResetEvent فقط واحد من المسارات المنتظرة عندما يصبح في حالة إشارة بينما الغرض ManualResetEvent يوقظ جميع المسارات المنتظرة ويجب أن يتم إعادة ضبطه يدويا إلى حالة عدم إشارة كما هو ظاهر من اسمه وكما ذكر سابقا يمكنك استبدال الغرضين ManualResetEvent و AutoResetEvent بالغرض EventWaitHandle كما يظهر بالكود التالي

كود :
' These statements are equivalent to the previous code example.
Dim are As New EventWaitHandle(False, EventResetMode.AutoReset)
Dim mre As New EventWaitHandle(True, EventResetMode.ManualReset)
وتكون أغراض الـ Event مفيدة خاصة في حالات المنتج والمستهلك فربما يكون لديك إجراء وحيد في مسار يقوم بتقييم بعض البيانات أو بالقراءة من القرص أو منفذ تسلسلي أو غيرها ويستدعي الطريقة Set على غرض متزامن فيتم إعادة تشغيل مسار أو أكثر لمعالجة تلك البيانات ويجب عليك استخدام الغرض AutoResetEvent أو الغرض EventWaitHandle مع الخيار AutoReset إذا كان هناك مسار مستهلك وحيد سيقوم بمعالجة تلك البيانات كما يجب عليك استخدام الغرض ManualResetEvent أو الغرض EventWaitHandle مع الخيار ManualReset إذا كان يجب معالجة البيانات باستخدام جميع المسارات المستهلكة.
ويبين المثال التالي كيف يمكن أن يكون لديك عدة مسارات منتجة تقوم بعملية البحث عن ملف في عدة مجلدات مختلفة في نفس الوقت ولكن يوجد مسار مستهلك وحيد يقوم بجمع النتائج من تلك المسارات ويستخدم المثال الغرض AutoResetEvent لإيقاظ المسار المستهلك عندما يتم إضافة اسم ملف جديد للقائمة List(Of String) ويستخدم أيضا الفئة Interlocked لإدارة عدد المسارات العاملة حتى يعلم المسار الرئيسي أنه لم تعد توجد أي بيانات أخرى لاستهلاكها

كود :
' The shared AutoResetEvent object
Public are As New AutoResetEvent(False)
' The list where matching filenames should be added
Public fileList As New List(Of String)()
' The number of running threads
Public searchingThreads As Integer
' An object used for locking purposes
Public lockObj As New Object()

Sub TestAutoResetEvent()
' Search *.zip files in all the subdirectories of C.
For Each dirname As String In Directory.GetDirectories("C:\")
Interlocked.Increment(searchingThreads)
' Create a new wrapper class, pointing to a subdirectory.
Dim sf As New FileFinder()
sf.StartPath = dirname
sf.SearchPattern = "*.zip"
' Create and run a new thread for that subdirectory only.
Dim t As New Thread(AddressOf sf.StartSearch)
t.Start()
Next

' Remember how many results we have so far.

Dim resCount As Integer = 0
Do While searchingThreads > 0
' Wait until there are new results.
are.WaitOne()

SyncLock lockObj
' Display all new results.
For i As Integer = resCount To fileList.Count - 1
Console.WriteLine(fileList(i))
Next
' Remember that you've displayed these filenames.
resCount = fileList.Count
End SyncLock
Loop
Console.WriteLine("")
Console.WriteLine("Found {0} files", resCount)
End Sub
وكل مسار إجرائي يعمل ضمن غرض FileFinder مختلف الذي يجب أن يكون قادرا على الوصول إلى متغيرات عامة محددة في الكود السابق

كود :
Class FileFinder
Public StartPath As String ' The starting search path
Public SearchPattern As String ' The search pattern

Sub StartSearch()
Search(Me.StartPath)
' Decrease the number of running threads before exiting.
Interlocked.Decrement(searchingThreads)
' Let the consumer know it should check the thread counter.
are.Set()
End Sub

' This recursive procedure does the actual job.
Sub Search(ByVal path As String)
' Get all the files that match the search pattern.
Dim files() As String = Directory.GetFiles(path, SearchPattern)
' If there is at least one file, let the main thread know about it.
If files IsNot Nothing AndAlso files.Length > 0 Then
' Ensure found files are added as an atomic operation.
SyncLock lockObj
' Add all found files.
fileList.AddRange(files)
' Let the consumer thread know about the new filenames.
are.Set()
End SyncLock
End If

' Repeat the search on all subdirectories.
For Each dirname As String In Directory.GetDirectories(path)
Search(dirname)
Next
End Sub
End Class
والنسخة 2 من الفريموورك وفيجول بايزيك 2005 تستخدم EventWaitHandle بدلا عن AutoResetEvent أو ManualResetEvent مما يعطيك ميزة هامة وهي إمكانية إنشاء غرض مسمى على مستوى النظام يمكن مشاركته مع العمليات الأخرى والصيغة العامة لباني EventWaitHandle مشابهة لتلك الخاصة بالفئة Mutex

كود :
Create a system-wide auto reset event that is initially in the signaled state.
Dim ownership As Boolean
Dim ewh As New EventWaitHandle(True, EventResetMode.AutoReset, "eventname", ownership)
If ownership Then
' The event object was created by the current thread.

End If
كما يمكنك استخدام الطريقة OpenExisting لفتح غرض حدث موجود

كود :
' This statement throws a WaitHandleCannotBeOpenedException if the specified
' event doesn't exist, or an UnauthorizedAccessException if the current
' user doesn't have the required permissions.
ewh = EventWaitHandle.OpenExisting("eventname", EventWaitHandleRights.FullControl)
والميزة الأخرى الهامة في أغراض الأحداث event objects في الفريموورك 2 هي دعم ACLs باستخدام الطرائق SetAccessControl و GetAccessControl والتي تأخذ وتعيد كائن من النوع EventWaitHandleSecurity حيث يمكنك استخدامه بنفس طريقة استخدام مثيلاتها في الغرض Mutex وأغراض الدوت نيت الأخرى التي تدعم ACLs
}}}
تم الشكر بواسطة:


المواضيع المحتمل أن تكون متشابهة .
الموضوع : الكاتب الردود : المشاهدات : آخر رد
  التعامل مع Resources و image list الاستغناء عن المسارات RaggiTech 1 3,237 02-10-12, 10:58 AM
آخر رد: RaggiTech
  استخدام بحيرة المسارات Using the Thread pool RaggiTech 0 2,137 02-10-12, 01:36 AM
آخر رد: RaggiTech
  Thread Methods &amp; Proprties &amp; Attributes RaggiTech 0 1,687 01-10-12, 07:33 PM
آخر رد: RaggiTech

التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 1 ) ضيف كريم