Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 443
Bob Horn Avatar asked Apr 16 '12 20:04

Bob Horn


3 Answers

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.

like image 93
phoog Avatar answered Oct 11 '22 11:10

phoog


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);
        }
    }
}
like image 38
Sergey Berezovskiy Avatar answered Oct 11 '22 13:10

Sergey Berezovskiy


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);
        }
    }
}
like image 37
Brian Gideon Avatar answered Oct 11 '22 12:10

Brian Gideon