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