views:

949

answers:

3

I'm writing a special data structure that will be available in a .NET library and one of the features of this data structure is that is will be thread safe provided that only one thread writes data to it, and only one thread reads data from it (the reader thread and the writer thread can be different).

The question is how can I enforce that all Read operations are executed by the same thread?

My solution would be capture the System.Threading.Thread.ManagedThreadID and store it in a private member upon the first Read. Then, on subsequent reads to check the ManagedThreadID against the saved one and if they are different to throw an exception.

Is that enough, or is there a different more reliable mechanism for doing this.

Note: There is a requirement that this library be usable without a Windows.Forms context..

+1  A: 

Could you not require the Read and Write methods take a thread or thread ID? Then you can just compare against the one that called it first, and if it doesn't match, throw an exception or return an error code, or ignore the request.

Otherwise, what you propose should work also. You need only compare the thread IDs.

Jeff Yates
I wouldn't want to rely on what the caller of the Read/Write methods provides as input.. Otherwise, if I can reliably use the ManagedThreadID method I suppose I'll use that. Thanks.
Miky Dinescu
+1  A: 

Rather than compare thread ID's you should store the ambient Thread in your class during construction.

class SingleThreadedClass
{
    private Thread ownerThread;

    public SingleThreadedClass()
    {
        this.ownerThread = Thread.CurrentThread;
    }

    public void Read(...)
    {
        if (Thread.CurrentThread != this.ownerThread)
            throw new InvalidOperationException();
    }

    public void TakeOwnership()
    {
        this.ownerThread = Thread.CurrentThread;
    }
}
sixlettervariables
Is there any specific reason why I should use a reference to the Thread as opposed to the thread ID? Your technique is very similar to mine except it uses references to threads instead of IDs. Just curious..
Miky Dinescu
I worry about the situation where a managed thread ID gets recycled (improbable?). You can also do more with the Thread than the managed ID.
sixlettervariables
+4  A: 

When I run into this situation I use a class I wrote called ThreadAffinity. It's entire purpose is to record the current thread and throw on invalid accesses from a different thread. You have to manually do the check but it encapsulates the small amount of work for you.

class Foo {
  ThreadAffinity affinity = new ThreadAffinity();

  public string SomeProperty {
    get { affinity.Check(); return "Somevalue"; }
  }
}

Class

[Immutable]
public sealed class ThreadAffinity
{
    private readonly int m_threadId;

    public ThreadAffinity()
    {
        m_threadId = Thread.CurrentThread.ManagedThreadId;
    }

    public void Check()
    {
        if (Thread.CurrentThread.ManagedThreadId != m_threadId)
        {
            var msg = String.Format(
                "Call to class with affinity to thread {0} detected from thread {1}.",
                m_threadId,
                Thread.CurrentThread.ManagedThreadId);
            throw new InvalidOperationException(msg);
        }
    }
}

Blog post on the subject:

JaredPar
Thanks Jared. Just curious, do you see any reason @sixlettervariables's answer would be better/worse?
Miky Dinescu
@Miky D, the only reason I would say it's worse is because it's mixing responsibilities. Having a separate class for checking for invalid cross thread access provides a consistent an reusable experience. It's a very small amount of code though and the only thing you can really screw up is the error message so IMHO it's not a terribly big deal.
JaredPar
I agree with JaredPar as far as using a class, just not with using the Managed ID. But yes, +1 use another class to handle the logic.
sixlettervariables