0

I have the following context:

private void btn_Click(object sender, EventArgs e)
{
    Thread newThread = new Thread(Step1);
    newThread.Start(this);
}

private void Step1(object stateInfo)
{
    Register();

    // work

    Thread newThread = new Thread(Step2);
    newThread.Start(this);
}

private void Step2(object stateInfo)
{
    Register();

    // work

    Thread newThread = new Thread(Step3);
    newThread.Start(this);
}

private void Step3(object stateInfo)
{
    Register();

    // work
    // finish
}

private void Register()
{
    // adds a new entry to a Dictionary,
    // where the key is the Thread's ID (System.Threading.Thread.CurrentThread.ManagedThreadId)
    // and the value is a resource 
    // the goal is for a Thread not to register itself twice, or not to change its resource 
}

As the comments say, I need for each Thread to save a resource. This should be done only once per Thread and shouldn't be modifiable.

The problem is that, sometimes, the Thread from Step3 has the same ID as the Thread from Step1. I think it makes sense; because by the time it gets to Step3, Step1's Thread is already 'dead' and its ID can be reused.

Do you have any suggestions? Is there something wrong with my approach? Is there a way of obtaining/generating an unique ID for all the Threads that a Process creates?

[Edit 1] Included the .NET 2.0 constraint.

Yeseanul
  • 2,787
  • 4
  • 21
  • 25
  • 1
    Sounds like a bug to me. When do IDs get unregistered? If not at thread-exit then the thread ID is clearly useless. – Hans Passant Mar 06 '14 at 21:25
  • IDs don't get unregistered. And they shouldn't (this is a design request). – Yeseanul Mar 06 '14 at 22:36
  • @HansPassant As long as the IDs are large enough to run out (e.g. a 64 bit counter or a GUID), there isn't really a need to unregister it. Avoiding reused IDs can be useful for debugging. – CodesInChaos Mar 06 '14 at 23:30
  • He's using Thread.ManagedId, somewhere between this "stop thinking about it, this is my job" designer and this programmer, something got lost in translation. – Hans Passant Mar 06 '14 at 23:58
  • 1
    Why do threads need to register? There's probably a better way to do this. – bmm6o Mar 07 '14 at 00:00
  • Have you considered using thread local storage? – Lasse V. Karlsen Mar 07 '14 at 15:46
  • @LasseV.Karlsen In what particular way (`ThreadLocal` is >= 4.0)? – Yeseanul Mar 07 '14 at 17:43
  • 1
    @Yeseanul The `[ThreadStatic]` attribute works in older versions. In my tests it had better performance as well. TLS works well if the only place you need to access the ID is in the tread itself. If you need the capability to query the ID of another thread, it doesn't work. – Loki Mar 07 '14 at 19:30

3 Answers3

1

You should probably use a ThreadLocal<T> object or a ThreadStatic static field, these will have their content erased when the thread dies, and won't be reused even if a new thread with the same managed thread ID spawns.

See here: ThreadLocal and ThreadStatic

M.A. Hanin
  • 8,044
  • 33
  • 51
1

Threads are objects so you can simply rely on the built-in equality system:

private Dictionary<Thread, R> store = new Dictionary<Thread, R> ();
private void Register()
{
    var r = ..
    store.Add(Thread.CurrentThread, r);
}

reference equality will ensure that you won't have duplicates. You should be aware though that your dictionary is blocking the Threads from being GCed. You will need to clean up once in a while.

And if you don't want to keep the Threads around, then create your own simple id generator with Interlocked.Increment()

H H
  • 263,252
  • 30
  • 330
  • 514
  • 1
    A couple of years ago I asked a similar question on SO since I also worried about the GC and received [a great response](http://stackoverflow.com/a/6633940/445517): .NET 4.0 added explicit support for ephemerons, `ConditionalWeakTable` doesn't prevent the GC from collecting its keys and values. It's thus an ideal choice to add some kind of "tag" to arbitrary objects. – CodesInChaos Mar 06 '14 at 23:27
  • Yes, but the OP is completely unclear about the (desired) lifetime of the Thread objects. – H H Mar 07 '14 at 09:21
  • The lifetime is not an issue/requirement. The only thing is that Threads that are alive should be able to register only once. If a Thread terminates, I don't care about his registered object. – Yeseanul Mar 07 '14 at 17:47
  • If that was true you wouldn't mind that the Step3 thread and Step1 thread had the same Id. You do need to think about your requirements and constraints more clearly. – H H Mar 07 '14 at 20:58
1

To avoid the GC problem @Henk's code has, you can use a ConditionalWeakTable (available on .NET 4.0 an later), which is doesn't prevent the GC from collecting its keys and values. I use longs for the IDs, since that way we don't need to worry about integer overflows in long running applications.

public class UniqueId<T>
    where T:class
{
    long counter = 0;
    ConditionalWeakTable<T, object> ids = new ConditionalWeakTable<T,object>();

    public long GetId(T obj)
    {
        return (long)ids.GetValue(obj, _ => Interlocked.Increment(ref counter));
    }
}

For your problem you can use:

var ids = new UniqueId<Thread>();

ids.GetId(thread1) // will assign it the id 1
ids.GetId(thread2) // will assign it the id 2 (assuming it's a different thread)
ids.GetId(thread1) // will reuse the existing id 1

This code is thread-safe since Interlocked.Increment generates unique ids and ConditionalWeakTable is threadsafe as well. But it can skip ids if several threads query the id of a thread that has no id yet. Like the documentation for GetValue says:

If multiple threads try to create the same key, createValueCallback may be invoked multiple times with the same key. Only one of these calls will succeed, and its returned value will be added to the table. Which thread succeeds in creating the value is indeterminate.

This should be fine for your application, but if it's unacceptable you can use a lock to prevent multiple threads from generating an ID at the same time.

Loki
  • 421
  • 2
  • 4
  • Any alternatives for .NET 2.0? – Yeseanul Mar 06 '14 at 22:41
  • 1
    @Yeseanul You could use Henk's approach and write a cleanup function that occasionally removes terminated threads from the dictionary. If you don't need to get the ID from another thread, only the currently executing one you can use thread local storage as M.A. Hanin suggests. – Loki Mar 06 '14 at 22:43
  • @Yeseanul - when you need an answer for 2.0, be very specific about it in the question. We're in the 4.51 era now. – H H Mar 07 '14 at 07:55