Thread safety

Introduction

In one of my older projects, I often had to synchronize and work with different parallel threads. Using async, parallel or 'real' threads does not make a real difference when it is coming to thread safety.

As a very short short instruction to thread safety: Every value is stored in memory somewhere, somehow. The location in memory has got an address, so called memory address. When you set the value once and only read from it in the rest of the program you won't have any problems (e.g. const). But if you set the value multiple times and read from it, you do not know which result you will get as all threads are accessing the same memory address at the same time.

Let us take an example, you have a string in memory containing '1234567890' and re-writing this the whole time to '0987654321'. It does require time to set the value in memory and reading is much faster. When you read this value in parallel from another thread you will never know which value you get. It may also happen that you get 'garbage'.

This article does only cover handling variables not the program flow itself. When working in parallel, always make sure that all parts of your application or area can be handled parallel (ServiceProvider / external resources).

Integer

Integers can be implemented quite simple using Interlocked and the corresponding methods.

The main method I mostly work with is Exchange(), please just take a look at the ThreadSafeBool class further down.

Boolean

Following just a short dump of a thread safe bool class implemented using Interlocked.

public class ThreadSafeBool
    {
        /// <summary> internal storage </summary>
        private int _internalBackingDoNotUse;

        /// <summary> Use interlock exchange for thread safety </summary>
        private bool ThreadSafeBoolValue
        {
            get => Interlocked.CompareExchange(ref _internalBackingDoNotUse, 1, 1) == 1;
            set
            {
                if (value) Interlocked.CompareExchange(ref _internalBackingDoNotUse, 1, 0);
                else Interlocked.CompareExchange(ref _internalBackingDoNotUse, 0, 1);
            }
        }

        public static bool operator true(ThreadSafeBool tsb)
        {
            return tsb.ThreadSafeBoolValue;
        }

        public static bool operator false(ThreadSafeBool tsb)
        {
            return !tsb.ThreadSafeBoolValue;
        }

        public ThreadSafeBool()
        {
        }

        public ThreadSafeBool(bool value)
        {
            ThreadSafeBoolValue = value;
        }

        public static implicit operator ThreadSafeBool(bool value)
        {
            ThreadSafeBool tsb = new ThreadSafeBool
                {
                    ThreadSafeBoolValue = value
                };
            return tsb;
        }

        /// <summary> 
        /// operator for using retrieving
        /// </summary>
        /// <param name="tsb"></param>
        public static implicit operator bool(ThreadSafeBool tsb)
        {
            return tsb.ThreadSafeBoolValue;
        }

        /// <inheritdoc />
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        /// <inheritdoc />
        public bool Equals(bool other)
        {
            return ThreadSafeBoolValue == other;
        }

        /// <inheritdoc />
        public override int GetHashCode()
        {
            return ThreadSafeBoolValue ? 1 : 0;
        }
    }
    [TestClass]
    public class ThreadSafeBoolTests
    {
        [TestMethod]
        public void TestMethod1()
        {
            ThreadSafeBool tsb = false;

            bool currentValue = tsb;
            Assert.AreEqual(currentValue, false);

            if (tsb)
                Assert.Fail();
            if (tsb == true)
                Assert.Fail();


            tsb = true;
            currentValue = tsb;
            Assert.AreEqual(currentValue, true);

            if (!tsb)
                Assert.Fail();
            if (tsb == false)
                Assert.Fail();
        }
    }

Lock

Using the lock statement we can use a simple object as some kind of 'blocker'. As in, we use this object to synchronize our requests. It is required to ensure that all access to the variable is only done using the lock statement.

Following a short example with lock and a string by using properties ensuring the access to the variable only inside the lock:

public class Program
{
    private object _sampleLockedStringLock = new object();
    private string _sampleLockedString = string.Empty;
    public string SampleLockedString
    {
        get
        {
            lock(_sampleLockedStringLock)
            {
                return _sampleLockedString;
            }
        }
        set 
        { 
            lock(_sampleLockedStringLock)
            {
                if (_sampleLockedString == value)
                    return;

                _sampleLockedString = value;
            }
        }
    }
}

ASP.NET IMemoryCache

For web projects a great thread safe alternative for caches is IMemoryCache. Providing easy setter/getter and even expiration for the cache entries!

Thread safe collections

To round up, please also refer to thread safe collections and the great article when to use them.

Do not forget, that the collection itself is thread safe, but not the content if you are storing reference types inside.