2012-04-16 2 views
7

У меня есть ситуация, когда для тестирования я хочу, чтобы мой метод таймера (FooMethod) запускался по одному за раз. В приведенном ниже примере FooMethod передается как делегат на таймер. Существует много конкретных примеров этого класса. Я думал, что, ставя _locker static, только один экземпляр FooMethod() будет обрабатывать одновременно. Но когда я запускаю приложение, несколько потоков проходят через строку TryEnter() за раз.Monitor.TryEnter с общим классом

Вот как я добавляю каждый класс к новому таймеру. Это делается в цикле, для каждого экземпляра Foo:

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

И это класс, который имеет этот метод:

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); 
     } 
    } 
} 

Примечание: Как правило, _locker не статична; Я не хочу, чтобы тот же самый поток вводил метод до того, как он получил шанс завершить. Я изменил его на статику здесь для тестирования.

Моя первая мысль заключается в том, что, возможно, это не работает, потому что класс является общим? И что каждый конкретный класс на самом деле является его собственным классом, и они не разделяют переменную _locker? Это правда? Если это так, как я должен иметь конкретные классы, разделяющие переменную _locker? Нужно ли добавлять статическую переменную _locker в какой-то другой класс, к которому имеют доступ Foos?

+0

очень интересный и хорошо заданный вопрос. +1 – spender

+2

Обратите внимание, что тот же поток * может * повторно ввести блокировку (но только если есть определенная рекурсия, конечно).Это то, что блокирует для: предотвращения * разных * потоков от использования одного и того же ресурса. – phoog

+0

@spender Спасибо. И я понимаю, что я знаю. Моя цель со статической переменной состояла в том, чтобы предотвратить одновременное обработку нескольких потоков одним и тем же способом. Но это не работает. –

ответ

7

Нужно ли добавить статическую переменную _locker в какой-либо другой класс в , к которой у Foos есть доступ?

Да.

Каждый закрытый тип Foo<T> с различными аргументами T имеет свой собственный статический объект _locker. Вы можете наследовать Foo из базового класса и поставить там статический объект. Затем все типы будут использовать один и тот же экземпляр.

6

Может

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); 
     } 
    } 
} 
+0

Спасибо. Это хорошая иллюстрация элегантного способа решения этой проблемы. –

0

Пожалуйста, этот класс как необщего типа. Это потребует ваших нужд.

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); 
     } 
    } 
} 
+0

Но у меня этот класс как общий по причине. Я не хочу отменять это. –

2

Вы верны. Каждый уникальный тип T, ссылающийся на код, заставляет CLR генерировать новый конкретный тип для Foo<T>, и каждый из них имеет свой собственный набор статических элементов.

Вы можете перестроить свой код, чтобы выглядеть следующим образом. Это лишь одна из многих допустимых вариаций.

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); 
    } 
} 

Кроме того, имейте в виду, что вы можете запустить таймер с бесконечным period для предотвращения обратного вызова от выполнения более чем один раз. Еще раз вызовите Change в конце FooMethod, чтобы снова поставить очередь в таймер. Поскольку у вас есть несколько таймеров, все сразу идут, вы будете иметь одновременно несколько одновременных исполнений FooMethod, но по крайней мере сейчас будет только один активный вызов на каждый таймер. Это не совсем то, о чем вы просили, но я думал, что все равно укажу.

_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); 
     } 
    } 
} 
+0

Хороший материал. Спасибо, Брайан! –

+0

Кстати, я должен указать, что мой второй пример имеет довольно серьезное состояние гонки. 'FooMethod' может быть вызван до того, как таймер будет добавлен в' List'. Не обращайте внимания на тот факт, что я читаю и пишу в эту коллекцию тем же, что явно не является потокобезопасным. Очевидно, что мой второй пример нуждается в дополнительной работе, но вы поняли эту идею. –

Смежные вопросы