2012-04-16 16 views
7

Ich habe eine Situation, in der ich zum Testen nur meine Timer-Methode (FooMethod) nacheinander ausführen lassen möchte. Im folgenden Beispiel wird FooMethod als Delegat an einen Zeitgeber übergeben. Es gibt viele konkrete Instanzen dieser Klasse. Ich dachte, dass, indem _locker statisch gemacht würde, nur eine Instanz von FooMethod() zu einer Zeit verarbeiten würde. Aber wenn ich die App starte, kommen mehrere Threads gleichzeitig an der TryEnter() Zeile vorbei.Monitor.TryEnter mit generischer Klasse

So füge ich jede Klasse zu einem neuen Timer hinzu. Dies geschieht in einer Schleife für jedes foo Beispiel:

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

Und das ist die Klasse, die diese Methode hat:

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

Hinweis: Normalerweise _locker ist nicht statisch; Ich möchte nicht, dass der gleiche Thread die Methode eingibt, bevor sie abgeschlossen werden konnte. Ich habe es hier zum Testen auf statisch geändert.

Mein erster Gedanke ist, dass das vielleicht nicht funktioniert, weil die Klasse generisch ist? Und dass jede konkrete Klasse tatsächlich ihre eigene Klasse ist und sie die Variable _locker nicht teilen? Ist das wahr? Wenn das stimmt, wie soll ich die konkreten Klassen eine _locker Variable teilen lassen? Muss ich eine statische _locker-Variable zu einer anderen Klasse hinzufügen, auf die die Foos Zugriff haben?

+0

sehr interessante und gut gestellte Frage. +1 – spender

+2

Beachten Sie, dass der gleiche Thread * kann eine Sperre wieder eingeben (aber nur wenn es natürlich eine Rekursion gibt).Das sind Sperren, die verhindern sollen, dass * verschiedene * Threads dieselbe Ressource verwenden. – phoog

+0

@spender Danke. Und phoog, ich verstehe. Mein Ziel mit der statischen Variable war es, zu verhindern, dass mehrere Threads diese Methode gleichzeitig verarbeiten. Aber das funktioniert nicht. –

Antwort

7

Muss ich eine statische _locker-Variable zu einer anderen Klasse zu hinzufügen, auf die die Foos Zugriff haben?

Ja.

Jeder geschlossene Foo<T> Typ, mit verschiedenen T Argumente, hat sein eigenes statisches _locker Objekt. Sie könnten Foo von einer Basisklasse erben lassen und das statische Objekt dorthin setzen. Dann würden alle Typen dieselbe Instanz verwenden.

6

diese Klasse als nicht-generischer Typ machen

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

Danke. Dies ist ein gutes Beispiel für eine elegante Lösung. –

0

Bitte Vielleicht. Das würde deine Bedürfnisse erfüllen.

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

Aber ich habe diese Klasse als generisch aus einem Grund. Ich möchte das nicht rückgängig machen. –

2

Sie haben Recht. Jeder im Code referenzierte eindeutige Typ T bewirkt, dass die CLR einen neuen konkreten Typ für Foo<T> generiert und jeder hat seinen eigenen Satz statischer Member.

Sie könnten Ihren Code so umstrukturieren, dass er wie folgt aussieht. Es ist nur eine von vielen gültigen Variationen.

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

Auch bedenken Sie, dass Sie den Timer mit einer unendlichen period beginnen den Rückruf zu verhindern, dass mehr als einmal ausgeführt wird. Rufen Sie erneut Change am Ende von FooMethod auf, um den Timer erneut in die Warteschlange zu stellen. Da Sie mehrere Timer gleichzeitig haben, haben Sie immer noch mehrere gleichzeitige Ausführungen von FooMethod gleichzeitig, aber zumindest gibt es nur einen aktiven Anruf pro Timer. Das ist nicht genau das, wonach du gefragt hast, aber ich dachte, ich würde das trotzdem betonen.

_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

Gute Sachen. Danke, Brian! –

+0

Übrigens sollte ich darauf hinweisen, dass mein zweites Beispiel eine ziemlich ernste Wettlaufsituation hat. 'FooMethod' könnte aufgerufen werden, bevor der Timer zu' List' hinzugefügt wird. Vergiss die Tatsache, dass ich gleichzeitig zu dieser Sammlung lese und schreibe, was eindeutig nicht threadsicher ist. Offensichtlich benötigt mein zweites Beispiel einige zusätzliche Arbeit, aber Sie bekommen die Idee. –

Verwandte Themen