Synchronization C# Help

It is best to avoid synchronization issues by not sharing data between threads. Of course, this is not always possible. If data Sharing is necessary, you must use synchronization techniques so that only one
thread at a time accesses and changes shared state. Remember the s~tion issues with race conditions and deadlocks. If you don’t pay attention to these issues, the reason for problems in applications is hard to find because threading issues occur just from time to time.

This section discusses synchronization technologies that you can use with multiple threads:

¤ lock statement

¤  Interlocked class

¤  Monitor class

¤  W.ait handles

¤  Mutex

¤  Semaphore

¤   Events

¤   ReaderWriterLockSlim

lock, Interlocked, and Monitor can be used for synchronization within a process. The classes Mutex, Event, Semaphore, and ReaderWri terLockSlim also offer synchronization between threads of multiple processes.

lock Statement and Thread Safety

C# has its own keyword for the synchronization of multiple threads: the lock statement. The lock statement is an easy way to hold for a lock and release it. Before adding lock statements, let’s go into another race condition. The class SharedState just demonstrates using shared state between threads, and keeps an integer value:

The class Task contains the method DoTheTask ( ), which is the entry point for a new thread. With the implementation, the State of  haredState is incremented 50,000 times. The variable sharedState is
initialized in the constructor of this class

In the Main () method, a SharedState object is created and passed to the constructor of 20 Thread objects. All threads are started. After starting the threads, the Main () method does another loop to join
every one of the 20 threads to wait until all threads are completed. After the threads are completed, the summarized value of the shared state is written to the console. Having 50,000 loops and 20 threads, a
value of 1,000,000 could be expected. Often, however, this is not the case.

Results received from multiple runs of the application are as shown  here:

summarized 939270
summarized 993799
summarized 998304
summarized 937630

The behavior it,different every time, but none of the results is correct. Youget big differences between debug and release builds, and on the types of CPUs you are using. If you change the loop count for smaller values, you will get correct values many times – but not every time. The application is small enough to see the problem easily; the reason for such a problem can be hard to find in a large application.

You must add synchronization to this program. This can be done with the lock keyword.

The object defined with the lock statement means you wait to get the lock for the specified object. You can pass only a reference type. Locking a value type would just lock a copy, and this wouldn’t make any sense. Anyway, the ” compiler provides an error If value types are used with the lock statement. As soon as the lock Is granted – only one thread gets the lock – the block of the lock statement can run. At the end of the lock statement block, the lock for the object Is released, and another thread waiting for the lock can be granted.

To lock static members, you can place the lock on the type object:

You can make the instance members of a class thread-safe by using the lock ke~ord. This way, only bone thread at a time can access the methods DoThia () and DoThat () for the same instance.

However, because the object of the instance can also be used for synchronized access from the outside, and you can’t control this from the class itself, you can apply the SyncRoot pattern. With the SyncRoot
pattern, a private object named syncRoot is created, and this object is used with the lock statements:

You must bear in mind thatwhen using the SynchronizedDemo class,only methods aresynchronized. There isno synchronizationforinvoking two members of thisclass

Let’s compare this with the example shown earlier. If you try to make the SharedState class threadsafe by locking access to the properties with the SyncRoot pattern, you still get the race condition sl, ..om earlier.

The thread invoking the DoTheTask method is accessing the get accessor of the SharedState class to get the current value of the state, and then the get accessor sets the new value for the state. In between calling the get and the set accessor the object is not locked, and another thread can be the interim value.

So. it is better to leave the SharedState class as it was earlier without thread safety

and t~ add the lock statement where it belongs, inside the method DoTheTaalc( ):

This way, the results of the application are always as expected

summarIzed 100000

Of course, you can also change the design of the SharedState class and offer increment as an atomic operation. This is a design question – what should be an atomic functionality of the class?

There is, however, a faster way to lock the increment of the state, as shown next.

nterloeked

The Interlocked class is used to make simple statements for variables atomic. i++ is not thread-safe. i++ consists of getting a value from the memory, incrementing the value by I, and storing the value back
into memory. These operations can be interrupted by the thread scheduler. The Interlocked class provides methods for incrementing, decrementing, and exchanging values in a thread-safe manner. The methods provided by the Interlocked class are described in the following table.

Using the Interlocked class in contrast to other synchronization techniques is much faster. However, you can use it only for simple synchronization issues. For example, instead of using the lock statement to lock access to the variable someState when setting it to a new value, in case it is null, you can use the Interlocked class, which is faster

The faster version with the same functionality uses the Interlocked. Compare Exchange () method: Inter locked. Compare Exchange <SomeState> (resomeState, newState, null)

And instead of doing an increment.inside a lock statement

Interlocked. Increment () is faster:

MonItor

The CI compiler resolves the lock statement to use the Monitor class. The following Lock statement

is resolved to invoking the Enter () method that waits until the thread gets the lock of the object. Only one thread at a time may be the owner of the object lock. As soon as the lock is resolved, the thread can
enter the synchronized section. The Exi t () method of the Monitor class releases the lock. The compiler puts the Exi t () method into a finally handler of a try block so that the lock is also released if an
exception is thrown.

The class Monitor has a big advantage compared to the lock statement of C#: you can add a timeout value waiting to get the lock. So instead of endlessly waiting to get the lock, you can use the TryEnter () method, where you can pass a timeout value that defines the maximum amount of time waiting to get the l~k. If the lock for obj is acquired, TryEnter () returns true and performs synchronized access to the state guarded by the object obj. If obj is locked for more than 500
milliseconds by another thread, TryEnter () returns false, and the thread does not wait any longer but is used to do something else. Maybe at a later time, the thread can try to acquire the lock once more.

Walt Handle

wai tHandle is an abstract base class that you can use to wait for a signal to be set. There are differentthings you can wait for,because wai tHandle is a base class and some classes are derived from it.

In the use of asynchronous delegates early in this chapter, the wai tHandle was already in use. The method Beginlnvoke () of ‘the asynchronous delegate returns an object that implements the Interface
IAsyncResul t. Using IAsyncResul t, you can access a wai tHandle with the property AsyncWaitHandle. When you invoke the method wai tone ( ), the thread waits until a signal is received that is associated with the wait handle.

The methods that are defined by the class WaitHandle to perform a wait are described in the following table.

With the SafeWaitHandle property, you can also assign a native handle to an operating system resource and wait for that handle. For example, you can assign a SafeFileHandle to wait for a file I/O operation to complete, or a custom SafeTransactionHandle as shown in Chapter 22, “Transactions.” The classes Mutex, Event, and Semaphore are derived from the base class WaitHandle, so you can use all.of these with waits.

Mutex

Mutex (mutual exclusion) is one of the classes of the .NET Framework that offers synchronization across multiple processes. It is very similar to the Monitor class in that there is just one owner. Just one thread
can get a lock of the.mutex and access the synchronized ~ode regions that are secured by the mutex. With the constructor of the Mutex class, you can define if the mutex should initially be owned by the
calling thread, define a name of the mutex, and get the information if the mutex already existed. In the sample code, the third parameter is defined as an out parameter to receive a Boolean value if the mutex
was newly created. If the value returned is false, the mutex was already defined. The mutex might be defined in a different process, because a mutex with a name is known for the operating system and is shared between different processes. If there is not a name assigned to the mutex, the mutex is unnamed and not shared between different processes.

bool createdNew;
Mutex mutex = new Mutex(false, ·ProCSharpMutex·, out createdNew);

To open an existing mutex, you can also use the method Mutex. OpenExisting (), which doesn’t require the same .NETprivileges as creating the mutex with the constructor. Because the Mutex class derives from the base class WaitHandle, you can do a WaitOne () to acquire the mutex lock and be the owner of the mutex during that time. The mutexis released by invoking the ReleaseMutex () method.

Becausea named mutex is known system-wide, you can use it to not allow an application to be started twice. In the following Wmdows Forms application, the constructor of the Mutex object is invoked. Then
it is verified 1fthe mutex with the name SingletonWinAppMutex exists already. If it does, the application exits

Semaphore

A semaphore is very similar to a mutex, but, in contrast, the semaphore can be used by.multiple threads at once. A semaphore is a counting mutex, meaning that with a semaphore you can define the number of threads that are allowed to access the resource guarded by the semaphore simultaneously. This can be used if you have several of the resources available and can allow only a specific number of threads access to the resource. For example, say that you want to access physical I/O ports on the system and there are three ports available. So, three threads can access the I/O ports simultaneously, but the fourth thread needs to wait until the resource is released by one of the other threads.

In the sample application, in the Main () method six threads are created and one semaphore with a count of 4. In the constructor of the Semaphore class, you can define count for the number of locks that can be acquired with the semaphore (the second parameter) and the number of locks that are free initially (the first parameter). If the first parameter has a lower value than the second parameter, the difference between the values defines the already allocated semaphore count. As with the mutex, you can also assign a name to the semaphore to share it between different processes. Here, no name is defined with the semaphore, so it is used only within this process. After the Semaphore object is created, six threads
are startedsand they all get the same semaphore.

In the thread’s main method, ThreadMain ( ) , the thread does a wai tOne () to lock the semaphore. Remember, the semaphore has a count of 4, so four threads can acquire the lock. Thread 5 must wait and, here, the timeout of 600 milliseconds is defined for a maximum wait time. If the lock cannot be acquired after the wait time, the thread writes a message to the console and repeats the wait in a loop. As soon as the lock is made, the thread writes a message to the console, sleeps for some time, and releases the lock. Again, with the release of the lock it is important that the resource be released in all cases. That’s why the Release () method of the Semaphore class is invoked in a finally handler.

When you run the application, you can indeed see that with four threads the lock is made immediately. The threads with IDs 7 and 8 must wait. The wait continues in the loop until one of the other threads
releases the semaphore.

Thread 3 locks the semaphore
Thread 4 ~ocks the semaphore
Thread 5 locks the semaphore
Thread 6 locks the semaphore

Timeout for thread 8; wait again
Timeout for thread 7; wait again
Timeout for thread 8; wait again
Timeout for thread 7; wait again
Timeout for thread 7; wait again
Timeout for thread 8; wait again
Thread 3 releases the semaphore
Thread 8 locks the semaphore
Thread 4 releases the semaphore
Thread 7 locks the semaphore
Thread 5 releases the semaphore
Thread 6 releases the semaphore
Thread 8 releases the semaphore
Thread 7 releases the semaphore
All threads finished

Events

Events are the next e system-wide synchronization resources. For using system events from managed code, the.NET Framework offers the classes ManualResetEvent and AutoResetEvent in
the namespace System. Threading.

The event keyword from C# that was covered in Chapter 7 has nothing to do with the event classes from the namespace System. Threading. The event keyword is based on delegates, whereas both event classes are .NET torappers to the system-wide native event resource for synchroniZiltion

Youcan use events to inform other threads that some data is here, something is completed, and so on.

An event can be signaled or not signaled. A thread can wait for the event to be in a signaled state with
the help of the Wai~andle class, which was already discussed.
AManualResetEvent is signaled by invoking the Set () method and turned back to a non-signaled
state with the Reset () method. If multiple threads are waiting for an event to be signaled, and
the Set () method is invoked, then all threads waiting are released. Also, if a thread just invokes the
wai tOne () method, but the event is already Signaled, the waiting thread can continue immediately.

An AutoResetEvent is also signaled by invoking the Set () method. It is also possible to set it back to a non-signaled state with the Reset () method. However, if a thread is waiting for an auto-reset event to be signaled, the event is automatically changed into a non-signaled state when the wait state of the first thread is finished. The event changes automatically back into a non-signaled state.

This way, if multiple threads are waiting for the event to be set, only one thread is released from its wait state. It is not the thread that has been waiting the longest for the event to be signaled but the thread waiting with the highest priority.

Todemonstrate events with the AutoResetEvent class, the class ThreadTasJc defines the methodCalculation (), which is the entry point for a thread. With this method, the thread receives input datafor calculation (defined with the struct InputData) and writes the result to the variable ~ult that.can beaccessed from the Resul t property. As soon as the result is completed (aft~ random amount of time), . the event is signaled by invoking the Set () method of the AutoResetEvent.

The Main () method oftheprogram definesarraysoffourAutoResetEvent objects and four ThreadTask objects. Every ThreadTask isinitialized intheconstructorwith an AutoReaetEvent object, so thatevery threadgetsitS-ownevent object tosignalwhen itiscompleted. Now the
ThreadPool classisused tohave background threadsrunning thecalculationtasksby invoking the method QueueUserWo~kltem ().

The WaitHandle class is now used to wait for anyone of the events in the array. WaitAny () waits until .anyone of the events is signaled. wai tAny () returns an index value that provides information about the
event that was signaled. The returned value matches the index of the event array that is passed to WaitAny ( ) . Using this index the information from the signaled event can be read

Starting the application, you can see the threads doing the calculation and setting the event to inform the main thread that it can read the result. Depending on random times, whether the build is a debug or
release build, and your hardware, you might see different orders and also a different number of threads from the pool doing the tasks. Here, thread 4 was reused from the pool for doing two tasks because it
was fast enough to finish the calculation first:

Thread 3 starts calculation
Thread 4 starts calculation
Thcead 5 starts ~alculation
Tnread 4 is ready
finished task for 1. result: 6
Thread 4 starts calculation
Thread 3 is ready
finished task for 0, result: 4
Thread 4 is ready .
finished task for 3. result: 10
Thread 5 is ready
finished task for 2. result: 8

ReaderWrlterLockSllm

For a locking mechanism to allow multiple readers, but just one writer, to a resource, the class ReaderWriterLocltSlim can be used. This class offers a locking functionality in which multiple readers can access the resource if no writer locked it, and only a single writer can lock the resource

ReaderWri terLockSlim is new with .NET 3.0. The .NET 1.0 class with similar functionality is ReaderWri terLock. ReaderWri terLocltSlim was redesigne’d to prevent deadlo&s and to offer better performance.

The methods and properties of ReaderWri terLockSlim are explained in the following tables.

Properties of the Readerwri terLockSlim class give some status information about the current locks.

The sample program creates a collectioncontaining six items and a ReaderWri terLockSlim object.The method ReaderMethod () acquired a read lock to read allitems of the listand write itto the console. The method wri terMethod () triesto acquire a write lock to change allvalues of the collection.In the Main () method six threads are started that invoke either the method ReaderMethod () or the method WriterMethod()

With a run of the application here the first writer gets the lock first. The second writer and all readers need to wait. Next. the readers can work concurrently while the second writer still waits for the resource

Writer 1 acquired the lock
Writer 2 waiting for the write lock
current reader count: 0
Writer 2 waiting for the write lock
current reader count: 0
Writer 2 waiting for the write lock
current reader count: 0
Writer 2 walting for the write lock
current reader count: 0
Writer 1 finished
reader 4, loop: 0, item: 1
reader 1, loop: 0, item: 1
Writer 2 waiting for the write~lock
current reader count: 4
reader 2, loop: 0, item: 1
reader 3, loop: 0, item: 1
reader 4, loop: 1, item: 2
reader 1. loop:”1, item: 2
reader 3. loop: 1, item: 2
reader 2. loop: 1, item: 2
Writer 2 waiting. for the write lock
current reader count: 4
reader 4. loop: 2, item: 3
reader 1. loop: 2, item: 3
reader 2. loop: 2. item: 3
. reader 3. loop: 2. item: 3
Writer 2 waiting for the write lock
current reader count: 4
reader 4. loop: 3, item: 4
reader 1, loop: 3, item: 4
reader 2. loop: 3, item: 4

reader 3. loop: 3. item: 4
reader 4. loop: 4. item: 5
reader 1. loop: 4. item: 5
Writer 2 waiting for the write lock
current reader count: 4
reader 2. loop: 4. item: 5
reader 3. loop: 4. item: 5
reader 4. loop: 5. item: 6
reader 1. loop: 5. item: 6
reader 2. loop: 5. item: 6
reader 3. loop: 5. item: 6
Writer 2 waiting for the write lock
current reader count: 4
Writer 2 acquired the lock
Writer 2 finished

Timers

The .NETFramework offers several Timer classes that can be used to invoke a method after some time interval. The following table lists the Timer classes and their namespaces, as well as their functionality.

Using the System. Threading. Timer class, you can pass the method to be invoked as-the first _parameter in the constructor. This method must fulfill the requirements of the TimerCallback delegate that defines a void return type and an object parameter. With the second parameter, you can pass any object that is then received with the object argument in the callback method. For example, you can pass an Event object to signal the caller. The third parameter specifies the time span when the callback should be invoked the first time. With the last parameter, you specify the repeating interval for the callback. If
the timer should fire only once, set parameter four to the value-1.

If the time interval should be changed after creating the Timer object, you can pass new values with the Change () method.

The constructor of the Timer class from the Sys tem. Timers namespace requires just a time interval. The method that should be invoked after the interval is specified by the Elapsed event. This event requires a delegate of type ElapsedEventHandler that requires object and ElapsedEventArgs parameters as you can see with the TimeAction method. The AutoReset property specifies whether
the timer should be fired repeatedly. Setting this property to false, the event is fired only once. Calling the Start method enables the timer to fire the events. Instead of calling the Start method you can set the Enabled property to true. Behind the scenes Start () does nothing else. The Stop ( ) method sets the Enabled property to false to stop the timer.

 

 

 

 

 

 

Posted on November 3, 2015 in Threading and Synchronization

Share the Story

Back to Top