-1

I have been working through this article from the NHibernate website to implement a UnitOfWork pattern, and I have run into a problem I haven't been able to solve. In section 3, which is where the implementation is being made thread safe, there is a test which is throwing a null reference exception. I have no experience with multi-threading, so I'm not sure how to proceed here.

Can you tell me what the problem is?

Test code

private ManualResetEvent _event;

[Test]
public void Local_data_is_thread_local()
{
    Console.WriteLine("Starting in main thread {0}", Thread.CurrentThread.ManagedThreadId);
    Local.Data["one"] = "This is a string";
    Assert.AreEqual(1, Local.Data.Count);

    _event = new ManualResetEvent(false);
    var backgroundThread = new Thread(RunInOtherThread);
    backgroundThread.Start();

    // give the background thread some time to do its job
    Thread.Sleep(100); <<<<<<< ######## EXCEPTION AFTER THIS LINE #########
    // we still have only one entry (in this thread)
    Assert.AreEqual(1, Local.Data.Count);

    Console.WriteLine("Signaling background thread from main thread {0}", Thread.CurrentThread.ManagedThreadId);
    _event.Set();
    backgroundThread.Join();
}

private void RunInOtherThread()
{
    Console.WriteLine("Starting (background-) thread {0}", Thread.CurrentThread.ManagedThreadId);
    // initially the local data must be empty for this NEW thread!
    Assert.AreEqual(0, Local.Data.Count);
    Local.Data["one"] = "This is another string";
    Assert.AreEqual(1, Local.Data.Count);

    Console.WriteLine("Waiting on (background-) thread {0}", Thread.CurrentThread.ManagedThreadId);
    _event.WaitOne();
    Console.WriteLine("Ending (background-) thread {0}", Thread.CurrentThread.ManagedThreadId);
}

Code being tested

public interface ILocalData
{
    object this[object key] { get; set; }
    int Count { get; }
    void Clear();
}

public static class Local
{
    static readonly ILocalData _data = new LocalData();

    public static ILocalData Data
    {
        get { return _data; }
    }

    private class LocalData : ILocalData
    {
        [ThreadStatic]
        private static Hashtable _localData = new Hashtable();

        public object this[object key]
        {
            get { return _localData[key]; }
            set { _localData[key] = value; }
        }

        public int Count
        {
            get { return _localData.Count; } <<<<<<< ######## EXCEPTION HERE #########
        }

        public void Clear()
        {
            _localData.Clear();
        }
    }
}

Technically, the tests pass, but in the output, I can see that there is a null reference exception:

Run started: C:\Users\brainbolt\Documents\GitHub\NHibernateUnitOfWork\NHinbernateUnitOfWork.Testing\bin\Debug\NHinbernateUnitOfWork.Testing.dll
Starting in main thread 18
Starting (background-) thread 19
System.NullReferenceException: Object reference not set to an instance of an object.
   at NHibernateUnitOfWork.NHibernateUnitOfWork.Local.LocalData.get_Count() in c:\Users\brainbolt\Documents\GitHub\NHibernateUnitOfWork\NHibernateUnitOfWork\Local.cs:line 37
   at NHinbernateUnitOfWork.Testing.LocalData_Fixture.RunInOtherThread() in c:\Users\brainbolt\Documents\GitHub\NHibernateUnitOfWork\NHinbernateUnitOfWork.Testing\LocalData_Fixture.cs:line 73
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()
   at NHibernateUnitOfWork.NHibernateUnitOfWork.Local.LocalData.get_Count() in c:\Users\brainbolt\Documents\GitHub\NHibernateUnitOfWork\NHibernateUnitOfWork\Local.cs:line 37
   at NHinbernateUnitOfWork.Testing.LocalData_Fixture.RunInOtherThread() in c:\Users\brainbolt\Documents\GitHub\NHibernateUnitOfWork\NHinbernateUnitOfWork.Testing\LocalData_Fixture.cs:line 73
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()
Signaling background thread from main thread 18
NUnit VS Adapter 2.0.0.0 executing tests is finished
Amit Joshi
  • 15,448
  • 21
  • 77
  • 141
brainbolt
  • 616
  • 1
  • 10
  • 24

1 Answers1

1

The problem is here:

[ThreadStatic]
private static Hashtable _localData = new Hashtable();

The ThreadStatic attribute indicates that the value of _localData field will be unique for each thread. However, the initialization will be performed only once, in the type constructor, so only the thread on which the type constructor executed will have a non-null _localData. All other threads will have a null.

From the MSDN site linked above:

Do not specify initial values for fields marked with ThreadStaticAttribute, because such initialization occurs only once, when the class constructor executes, and therefore affects only one thread. If you do not specify an initial value, you can rely on the field being initialized to its default value if it is a value type, or to null if it is a reference type.

Jakub Lortz
  • 14,616
  • 3
  • 25
  • 39
  • I see. Thanks. So doing something like this instead: `[ThreadStatic] private static Hashtable _localData; private static Hashtable localData { get { if (_localData == null) { _localData = new Hashtable(); } return _localData; } }` – brainbolt Nov 03 '15 at 21:01
  • @brainbolt Yes, that will work. – Jakub Lortz Nov 03 '15 at 21:33