Sharing Data between multiple Threads in C# and .NET Framework

Once we employ many threads at a same time the data integrity becomes very important. We don’t want situation when two threads access same data and update it in a wrong way. For instance, if we have access to counter within multiple threads this counter may have lesser value than expected. The reason is in the way Threads handle updates to a variable. It goes through three atomic processes: 1. Load data into registry, 2. Update value in the registry, 3. Copy value from registry in to memory. We can end up in a situation when we load value at a same time from two or more threads and then update with the same value.

The only way out of this situation is to use Interlocked class

Methods

NameDescription
Add Adds two integers
Decrement Decrements one from a value
Exchange Exchanges two values
Increment Increments one to a value
Read Reads a 64-bit number

It can be used like that in order to increment by one for example
// Counter.Count = Counter.Count + 1;
Interlocked.Increment(ref Counter.Count);

This way data is updated as one atomic process versus three. In addition we can useVolatileRead and VolatileWrite classes that both usesInterlocked class. The only drawback of Interlocked is that it can’t work with all the types.

Synchronization Locks works better than Interlocked since it can deal with wider variety of situations. It works by applying lock(this) keyword.
public void UpdateCount()
{
    lock (this)
    {
        _mycount = _mycount + 1;
        if (Count % 2 == 0) // An even number
        {
          _myevenCount = _myevenCount + 1;
        }
    }
}

This way we can ensure that only one thread is working with the locked state and update will always be accurate. Lock(this) is nothing special in itself since it relies on Monitor class to do all of heavy lifting.

Method of Monitor

NameDescription
Enter Enters an exclusive lock
Exit Exits an exclusive lock
TryEnter Tries to create an exclusive lock
Wait Releases an exclusive lock

And lock(this) could easily be substituted with
public void UpdateCount()
{
    Monitor.Enter(this);
    try
    {
        _mycount = _mycount + 1;
        if (Count % 2 == 0) // An even number
        {
            _myevenCount = _myevenCount + 1;
        }
    }
    finally
    {
        Monitor.Exit(this);
    }
}

As with any locking situations we may end up having a deadlock. This is always happens if we lock resource A with one thread and then resource B with another and they are both waiting for resource B and A to be released. In order to overcome this deadlocking issue we can do two things. For instance, we can useMonitor.TryEnter to try and recover or retry or we can reduce amount of code and time resource is locked.

Other synchronization methods include several important classes.

1. ReaderWriterLock – the main principle of this class is that only one writer can have a lock on the resource and at the time it tries to acquire the lock all readers must release their locks.

Properties

NameDescription
IsReaderLockHeld Checks if reader has a lock
IsWriterLockHeld Checks if writer has a lock

Methods

NameDescription
AcquireReaderLock Acquires a reader lock
AcquireWriterLock Acquires a writer lock
DowngradeFromWriterLock Downgrades a writer lock
ReleaseReaderLock Releases a reader lock
ReleaseWriterLock Releases a writer lock
UpgradeToWriterLock Upgrades a reader lock

Example is
ReaderWriterLock myLock = new ReaderWriterLock();
int counter = 0;
try
{
    myLock.AcquireWriterLock(1000);
    try
    {
        Interlocked.Increment(ref counter);
    }
    finally
    {
        myLock.ReleaseWriterLock();
    }
}
catch (ApplicationException)
{
    Console.WriteLine("Failed");
}

2. Kernel objects synchronization with the help ofMutex, Semaphore, andEvent.

  1. Mutext allows doing synchronization across process boundaries and AppDomains.
  2. Semaphore may be used to throttle access to resource via set of threads.
  3. Event can notify across process boundaries and AppDomains that certain event occurred. AutoResetEvent and ManualResetEvent two classes that handle Event object.

All of the above mentioned classes are derived from WaitHandle which has the following properties and methods.

Properties

NameDescription
Handle Handles the kernel object's native system handle

Methods

NameDescription
Close Releases all resources
WaitOne Blocks the current thread

Mutex myMutex = null;
try // Try
{
    myMutex = Mutex.OpenExisting("MYMUTEX");
}
catch (WaitHandleCannotBeOpenedException)
{
    // Cannot open
}
if (myMutex == null) // Create it
{
    myMutex = new Mutex(false, "MYMUTEX");
}
Semaphore mySemaphore = null;
try // Try and open
{
    mySemaphore = Semaphore.OpenExisting("THESEMAPHORE");
}
catch (WaitHandleCannotBeOpenedException)
{
    // Cannot open
}
if (mySemaphore == null) // Create it
{
    mySemaphore = new Semaphore(0, 10, "THESEMAPHORE");
}
EventWaitHandle myEvent = null;
try // Try and open
{
    myEvent = EventWaitHandle.OpenExisting("THEEVENT");
}
catch (WaitHandleCannotBeOpenedException)
{
    // Cannot open
}
if (myEvent == null) // Create it
{
    myEvent = new EventWaitHandle(false, EventResetMode.AutoReset, "THEEVENT");
}