2008-11-24 10 views
6

Im Moment habe ich einige Funktionen, die wie folgt aussehen:Kann ich einer Funktion ein Attribut hinzufügen, um den Wiedereintritt zu verhindern?

private bool inFunction1 = false; 
public void function1() 
{ 
    if (inFunction1) return; 
    inFunction1 = true; 

    // do stuff which might cause function1 to get called 
    ... 

    inFunction1 = false; 
} 

ich sie so in der Lage sein möchten, zu erklären:

[NoReEntry] 
public void function1() 
{ 
    // do stuff which might cause function1 to get called 
    ... 
} 

Gibt es ein Attribut ich hinzufügen kann eine Funktion, um den Wiedereintritt zu verhindern? Wenn nicht, wie würde ich eine machen? Ich habe von AOP-Attributen gehört, die zum Hinzufügen von Code vor und nach Funktionsaufrufen verwendet werden können. Wären sie geeignet?

+0

Was ist der Umfang dieser Einschränkung in Anwesenheit von mehreren Threads und mehreren Objektinstanzen? Ist es möglich, dass function1 zu jedem Zeitpunkt nur durch einen einzelnen Thread für einzelne Objektinstanzen ausgeführt werden kann oder ist es entspannter? – Constantin

Antwort

1

Es ist kein solches Attribut vordefiniert. Sie können neue Attribute erstellen, aber das wird Ihnen nicht helfen. Das Problem besteht darin, dass das benutzerdefinierte Attribut verhindert, dass die Methode erneut aufgerufen wird, was meines Erachtens nicht machbar ist.

Die lock-Anweisung ist nicht das, was Sie wollen, da dies dazu führt, dass Aufrufe blockieren und warten und nicht sofort zurückkehren.

PS: Verwenden Sie einen Versuch ... schließlich Block in der obigen Probe. Andernfalls, wenn eine Ausnahme mitten in der Funktion ausgelöst wird, bleibt inFunction1 der Wert true und alle Aufrufe kehren sofort zurück.

z.B. :

0

Ich denke nicht, dass das möglich sein wird.

Am nächsten kommt das 'Synchronized'-Attribut, das jedoch alle nachfolgenden Aufrufe blockiert.

2

Sie können feststellen, dass Sie PostSharp verwenden können, um dies zu erreichen - zusammen mit den Vorschlägen von Anthony über die Verwendung von try/finally. Es wird jedoch wahrscheinlich chaotisch sein. Überlegen Sie auch, ob Sie die erneute Eingabe pro Thread oder pro Instanz wünschen. (Können mehrere Threads die Methode aufrufen, um damit zu beginnen, oder nicht?)

Es gibt nichts wie dies im Framework selbst.

1

Wenn dies für Gewindesicherheit ist, müssen Sie vorsichtig mit dieser Variable sein.

Es ist möglich, dass ein anderer Thread in die Funktion kommen und die Prüfung bestehen kann, bevor der erste Thread die Variable gesetzt hat.

Stellen Sie sicher, es wie so flüchtig markiert ist:

private volatile bool inFunction1 = false; 
+3

"volatile" wird diese Wettlaufsituation nicht beheben. – Constantin

5

Ohne Montage und IL Umschreiben, gibt es keine Möglichkeit für Sie ein benutzerdefiniertes Attribut erstellen, die den Code in der Art und Weise verändert, die Sie beschreiben.

Ich schlage vor, dass Sie stattdessen einen Delegate-basierten Ansatz verwenden, z. für Funktionen eines einzigen Argument:

static Func<TArg,T> WrapAgainstReentry<TArg,T>(Func<TArg,T> code, Func<TArg,T> onReentry) 
{ 
    bool entered = false; 
    return x => 
    { 
     if (entered) 
      return onReentry(x); 
     entered = true; 
     try 
     { 
      return code(x); 
     } 
     finally 
     { 
      entered = false; 
     } 
    }; 
} 

nimmt diese Methode die Funktion (es passt Func < Targ, T> vorausgesetzt - Sie können andere Varianten schreiben oder eine völlig generische Version mit mehr Aufwand) zu wickeln und eine alternative Funktion zum Aufrufen bei Wiedereintritt. (Die alternative Funktion könnte eine Ausnahme auslösen oder sofort zurückkehren, usw.) Anschließend rufen Sie im gesamten Code, in dem Sie normalerweise die übergebene Methode aufrufen würden, das von WrapAgainstReentry() zurückgegebene Delegat auf.

+0

Dies weist immer noch inhärente Probleme in Umgebungen mit mehreren Threads auf. Siehe meinen Beitrag zu den oben genannten flüchtigen Variablen. –

+0

Natürlich, Rob. Es ist nur eine Demo, da es nur eine sehr spezifische Funktionssignatur behandelt. –

14

Statt einen bool verwenden und direkt einstellen, versuchen, eine lange und die Interlocked-Klasse mit:

long m_InFunction=0; 

if(Interlocked.CompareExchange(ref m_InFunction,1,0)==0) 
{ 
    // We're not in the function 
    try 
    { 
    } 
    finally 
    { 
    m_InFunction=0; 
    } 
} 
else 
{ 
    // We're already in the function 
} 

Dies wird der Scheck Thread-sicher machen.

+1

Aus Neugier, gibt es einen besonderen Grund, hier "long" anstatt "int" zu verwenden? (Es gibt auch eine 'CompareExchange (ref int, int, int)' Überladung) – FunctorSalad

+2

Kein besonderer Grund. Ich benutze, um eine Menge von Win32-Programmierung zu tun, und die ineinandergreifenden Funktionen dort dauern lange. Die Verwendung eines int wird im obigen Beispiel keinen Unterschied machen. – Sean

+0

Sie wissen, dass in C# ein Long als Int64 definiert ist, oder? – Theraot

4

Sie könnten ein Postsharp Attribut bauen zu überprüfen, um zu sehen, ob der Name der Methode im aktuellen Stack-Trace ist

[MethodImpl(MethodImplOptions.NoInlining)] 
    private static bool IsReEntry() { 
     StackTrace stack = new StackTrace(); 
     StackFrame[] frames = stack.GetFrames(); 

     if (frames.Length < 2) 
      return false; 

     string currentMethod = frames[1].GetMethod().Name; 

     for (int i = 2; i < frames.Length; i++) { 
      if (frames[i].GetMethod().Name == currentMethod) { 
       return true; 
      } 
     } 

     return false; 
    } 
+0

Das würde nur funktionieren, wenn der Wiedereintritt auf dem gleichen Thread passiert - aber egal, sieht interessant aus. – Simon

+0

Ich würde nur den Wiedereinstieg in den gleichen Thread betrachten. Wenn Reentry Threads betrifft, was ist mit den App-Domains? – Bob

1

Dieser Thread ist ein bisschen alt, aber ich dachte, es wert sein würde bringt es in 2012, weil dieses Problem (mehr oder weniger) noch existiert. Ich konnte dieses Problem mithilfe von Proxy-Objekten lösen, die mit Reflection.Emit generiert wurden (speziell unter Verwendung von LinFu.DynamicProxy). Der LinFu-Artikel ist älter als dieser Artikel, also nehme ich an, dass alles, was besprochen wurde, relevant war, als es gefragt wurde (und doch irgendwie immer noch heute).

Ich habe LinFu verwendet, weil ich es bereits für andere Zwecke benutzt habe, aber ich bin mir sicher, dass einige der anderen verfügbaren DynamicProxy-Frameworks für Sie funktionieren würden (zB Castle.DynamicProxy) oder Sie können Ihre eigenen auf Reflection.Emit basieren (nicht für diejenigen mit schwachen Dispositionen). Sie bieten einen Mechanismus, der einen großen Teil der AOP-Rolle ausfüllt, während Sie die Kontrolle über Ihren Code behalten.

0

Sie sollten möglicherweise die erneute Eingabe vermeiden, indem Sie Ihr Design so ändern, dass es function1() niemals aufruft, bevor der vorherige Aufruf abgeschlossen ist. Mir scheint, dass eine Schicht von oben function1() fehlt.

+0

Ok @Rob Ich kann das schätzen. – Chris