Bob Horn
Bob Horn

Reputation: 34297

Monitor.TryEnter with Generic Class

I have a situation where, for testing, I only want my timer method (FooMethod) to run one at a time. In the example below, FooMethod is passed as the delegate to a timer. There are many concrete instances of this class. I thought that by making _locker static, only one instance of FooMethod() would process at a time. But when I run the app, multiple threads are getting past the TryEnter() line at a time.

This is how I'm adding each class to a new timer. This is done, in a loop, for each foo instance:

_timers.Add(new Timer(foo.FooMethod, null, 0, 10000));

And this is the class that has that method:

public class Foo<T>
{
    private static readonly object _locker = new object();

    public void FooMethod(object stateInfo)
    {
        // Don't let threads back up; just get out
        if (!Monitor.TryEnter(_locker)) { return; }

        try
        {
            // Logic here
        }
        finally
        {
            Monitor.Exit(_locker);
        }
    }
}

Note: Normally, _locker isn't static; I don't want the same thread entering the method before it got a chance to complete. I changed it to static here for testing.

My first thought is that maybe this isn't working because the class is generic? And that each concrete class is actually its own class and they don't share the _locker variable? Is that true? If that's true how should I have the concrete classes share a _locker variable? Do I need to add a static _locker variable to some other class to which the Foos have access?

Upvotes: 7

Views: 433

Answers (4)

Brian Gideon
Brian Gideon

Reputation: 48949

You are correct. Each unique type T referenced in code causes the CLR to generate a new concrete type for Foo<T> and each has its own set of static members.

You could restructure your code to look like the following. It is but one among many valid variations.

public class Foo
{
    private static readonly object _locker = new object();

    public void FooMethod(object stateInfo)
    {
        // Don't let threads back up; just get out
        if (!Monitor.TryEnter(_locker)) { return; }

        try
        {
            // Logic here
        }
        finally
        {
            Monitor.Exit(_locker);
        }
    }
}

public class Foo<T>
{
    public void FooMethod(object stateInfo)
    {
        Foo.FooMethod(stateInfo);
    }
}

Also, keep in mind that you can start the timer with an infinite period to prevent the callback from executing more than once. Call Change again at the end of FooMethod to queue the timer again. Since you have multiple timers all going at once you will still have multiple concurrent executions of FooMethod going simultaneously, but at least now there will only be one active call per timer. That is not exactly what you asked for, but I thought I would point this out anyway.

_timers.Add(new Timer(foo.FooMethod, _timers.Count, 10000, Timeout.Infinite));

public class Foo<T>
{
    public void FooMethod(object stateInfo)
    {
        try
        {
            // Logic here
        }
        finally
        {
            int index = (int)stateInfo;
            _timers[index].Change(10000, Timeout.Infinite);
        }
    }
}

Upvotes: 2

Cinchoo
Cinchoo

Reputation: 6322

Please make this class as non-generic type. That would purpose your need.

public class Foo
{
    private static readonly object _locker = new object();

    public void FooMethod(object stateInfo)
    {
        // Don't let threads back up; just get out
        if (!Monitor.TryEnter(_locker)) { return; }

        try
        {
            // Logic here
        }
        finally
        {
            Monitor.Exit(_locker);
        }
    }
}

Upvotes: 0

phoog
phoog

Reputation: 43046

Do I need to add a static _locker variable to some other class to which the Foos have access?

Yes.

Each closed Foo<T> type, with different T arguments, has its own static _locker object. You could make Foo inherit from a base class, and put the static object there. Then, all the types would use the same instance.

Upvotes: 7

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236248

Maybe

public class Foo
{
   protected static readonly object _locker = new object();
}

public class Foo<T> : Foo
{
    public void FooMethod(object stateInfo)
    {        
        if (!Monitor.TryEnter(_locker)) { return; }

        try
        {
            // Logic here
        }
        finally
        {
            Monitor.Exit(_locker);
        }
    }
}

Upvotes: 6

Related Questions