Skip to main content

This example shows how to program a Lazy-evaluated thread-safe shared state object.

//
// Implementing Lazy-evaluated shared states
// Chapter10\Recipe1
//
// How it works
//
// The first example show why it is not safe to use the UnsafeState object with
// multiple accessing threads. We see that the Construct method was called
// several times, and different threads use different values, which is obviously
// not right. To fix this, we can use a lock when reading the value, and if it
// is not initialized, create it first. It will work, but using a lock with
// every read operation is not efficient. To avoid using locks every time, there
// is a traditional approach called the double-checked locking pattern. We check
// the value for the first time, and if is not null, we avoid unnecessary
// locking and just use the shared object. However, if it was not constructed
// yet, we use the lock and then check the value for the second time, because it
// could be initialized between our first check and the lock operation. If it is
// still not initialized, only then we compute the value. We can clearly see
// that this approach works with the second example---there is only one call to
// the Construct method, and the first-called thread defines the shared object
// state.
//
// Please note that if the lazy-evaluated object implementation is thread-safe,
// it does not automatically mean that all its properties are thread-safe as
// well.
//
// If you add, for example, an int public property to the ValueToAccess object,
// it will not be thread-safe; you still have to use interlocked constructs or
// locking to ensure thread safety.
//
// This pattern is very common, and that is why there are several classes in the
// Base Class Library to help us. First, we can use the
// LazyInitializer.EnsureInitialized method, which implements the double-checked
// locking pattern inside. However, the most comfortable option is to use the
// Lazy<T> class that allows us to have thread-safe Lazy-evaluated, shared
// state, out of the box. The next two examples show us that they are equivalent
// to the second one, and the program behaves the same. The only difference is
// that since LazyInitializer is a static class, we do not have to create a new
// instance of a class as we do in the case of Lazy<T>, and therefore the
// performance in the first case will be better in some scenarios.
//
// The last option is to avoid locking at all, if we do not care about the
// Construct method. If it is thread-safe and has no side effects and/or serious
// performance impacts, we can just run it several times but use only the first
// constructed value. The last example shows the described behavior, and we can
// achieve this result by using another LazyInitializer. EnsureInitialized
// method overload.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Chapter10.Recipe1
{
    class Program
    {
        static void Main(string[] args)
        {
            var t = ProcessAsynchronously();
            t.GetAwaiter().GetResult();

            Console.WriteLine("Press ENTER to exit");
            Console.ReadLine();
        }

        static async Task ProcessAsynchronously()
        {
            var unsafeState = new UnsafeState();
            Task[] tasks = new Task[4];

            for (int i = 0; i < 4; i++)
            {
                tasks[i] = Task.Run(() => Worker(unsafeState));
            }

            await Task.WhenAll(tasks);
            Console.WriteLine(" --------------------------- ");

            var firstState = new DoubleCheckedLocking();
            for (int i = 0; i < 4; i++)
            {
                tasks[i] = Task.Run(() => Worker(firstState));
            }

            await Task.WhenAll(tasks);
            Console.WriteLine(" --------------------------- ");

            var secondState = new BCLDoubleChecked();
            for (int i = 0; i < 4; i++)
            {
                tasks[i] = Task.Run(() => Worker(secondState));
            }

            await Task.WhenAll(tasks);
            Console.WriteLine(" --------------------------- ");

            var thirdState = new Lazy<ValueToAccess>(Compute);
            for (int i = 0; i < 4; i++)
            {
                tasks[i] = Task.Run(() => Worker(thirdState));
            }

            await Task.WhenAll(tasks);
            Console.WriteLine(" --------------------------- ");

            var fourthState = new BCLThreadSafeFactory();
            for (int i = 0; i < 4; i++)
            {
                tasks[i] = Task.Run(() => Worker(fourthState));
            }

            await Task.WhenAll(tasks);
            Console.WriteLine(" --------------------------- ");
        }

        static void Worker(IHasValue state)
        {
            Console.WriteLine("Worker runs on thread id {0}", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("State value: {0}", state.Value.Text);
        }

        static void Worker(Lazy<ValueToAccess> state)
        {
            Console.WriteLine("Worker runs on thread id {0}", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("State value: {0}", state.Value.Text);
        }

        static ValueToAccess Compute()
        {
            Console.WriteLine("The value is being constructed on a thread id {0}", Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(TimeSpan.FromSeconds(1));
            return new ValueToAccess(string.Format("Constructed on thread id {0}", Thread.CurrentThread.ManagedThreadId));
        }

        class ValueToAccess
        {
            private readonly string _text;

            public ValueToAccess(string text)
            {
                _text = text;
            }

            public string Text
            {
                get
                {
                    return _text;
                }
            }
        }

        class UnsafeState : IHasValue
        {
            private ValueToAccess _value;

            public ValueToAccess Value
            {
                get
                {
                    if (_value == null)
                    {
                        _value = Compute();
                    }
                    return _value;
                }
            }
        }

        class DoubleCheckedLocking : IHasValue
        {
            private object _syncRoot = new object();
            private volatile ValueToAccess _value;

            public ValueToAccess Value
            {
                get
                {
                    if (_value == null)
                    {
                        lock (_syncRoot)
                        {
                            if (_value == null)
                            {
                                _value = Compute();
                            }
                        }
                    }
                    return _value;
                }
            }
        }

        class BCLDoubleChecked : IHasValue
        {
            private object _syncRoot = new object();
            private ValueToAccess _value;
            private bool _initialized = false;

            public ValueToAccess Value
            {
                get
                {
                    return LazyInitializer.EnsureInitialized(
                        ref _value, ref _initialized, ref _syncRoot, Compute);
                }
            }
        }

        class BCLThreadSafeFactory : IHasValue
        {
            private ValueToAccess _value;

            public ValueToAccess Value
            {
                get
                {
                    return LazyInitializer.EnsureInitialized(ref _value, Compute);
                }
            }
        }

        interface IHasValue
        {
            ValueToAccess Value { get; }
        }
    }
}