2009-11-21 6 views
14

Es gab eine Menge Diskussion darüber und jeder stimmt zu, dass Sie immer Delegate.EndInvoke aufrufen sollten, um ein Speicherleck zu verhindern (sogar Jon Skeet hat es gesagt!).Kein Aufruf von Delegate.EndInvoke kann Speicherverlust verursachen ... ein Mythos?

Ich folgte dieser Richtlinie immer ohne zu hinterfragen, aber vor kurzem implementierte ich meine eigene AsyncResult-Klasse und sah, dass die einzige Ressource, die auslaufen könnte, die AsyncWaitHandle ist.

(In der Tat ist es nicht wirklich leck, weil die native Ressource von der WaitHandle ist in einem SafeHandle verkapselt, die einen Finalizer hat, wird es Druck auf die Finalize-Warteschlange des Garbage Collector obwohl. Trotzdem, eine gute Implementierung von AsyncResult die AsyncWaitHandle auf Nachfrage ...)

der beste Weg, zu wissen, nur initialisieren, wenn ein Leck vorhanden ist, wird es nur versuchen:

Action a = delegate { }; 
while (true) 
    a.BeginInvoke(null, null); 

ich lief dies für eine Weile und dem Speicher Bleiben Sie zwischen 9-20 MB.

Vergleichen wir mit, wenn Delegate.EndInvoke genannt wird:

Action a = delegate { }; 
while (true) 
    a.BeginInvoke(ar => a.EndInvoke(ar), null); 

Mit diesem Test der Speicher Spiel zwischen 9-30 MG, komisch oder? (Wahrscheinlich, weil es etwas länger dauert, wenn ein AsyncCallback ausgeführt wird, so dass es mehr Delegierte in der ThreadPool-Warteschlange gibt)

Was denken Sie ... "Mythos kaputt"?

P.S. ThreadPool.QueueUserWorkItem ist hundert effizienter als Delegate.BeginInvoke, es ist besser, es für Feuer & Anrufe zu vergessen.

+2

Gute Frage! +1. BTW, du guckst zuviel MythBusters;) – RCIX

+0

Dem stimme ich zu, ThreadPool.QueueUserWorkItem ist eine geniale und wenig genutzte Methode. – Anton

Antwort

6

Ich habe einen kleinen Test ausgeführt, um einen Aktionsdelegaten aufzurufen und eine Ausnahme darin zu werfen.Dann stelle ich sicher, dass ich den Thread-Pool nicht überflute, indem ich nur eine bestimmte Anzahl von Threads gleichzeitig ausführe und den Thread-Pool kontinuierlich ausfülle, wenn ein Löschaufruf beendet wurde. Hier ist der Code:

static void Main(string[] args) 
{ 

    const int width = 2; 
    int currentWidth = 0; 
    int totalCalls = 0; 
    Action acc =() => 
    { 
     try 
     { 
      Interlocked.Increment(ref totalCalls); 
      Interlocked.Increment(ref currentWidth); 
      throw new InvalidCastException("test Exception"); 
     } 
     finally 
     { 
      Interlocked.Decrement(ref currentWidth); 
     } 
    }; 

    while (true) 
    { 
     if (currentWidth < width) 
     { 
      for(int i=0;i<width;i++) 
       acc.BeginInvoke(null, null); 
     } 

     if (totalCalls % 1000 == 0) 
      Console.WriteLine("called {0:N}", totalCalls); 

    } 
} 

Nach läßt es für etwa 20 Minuten laufen und mehr als 30 Millionen BeginInvoke rufen später der private Byte Speicherverbrauch war konstant (23 MB) sowie die Handleanzahl. Es scheint kein Leck vorhanden zu sein. Ich habe Jeffry Richters Buch C# über CLR gelesen, wo er sagt, dass es ein Speicherleck gibt. Zumindest scheint das mit .NET 3.5 SP1 nicht mehr zu stimmen.

Testumgebung: Windows 7 x86 .NET 3.5 SP1 Intel 6600 Dual Core 2,4 GHz

Mit freundlichen Grüßen Alois Kraus

2

In einigen Situationen benötigt BeginInvoke nicht EndInvoke (insbesondere in WinForms Window Messaging). Aber es gibt Situationen, in denen das wichtig ist - wie BeginRead und EndRead für asynchrone Kommunikation. Wenn Sie ein BeginWrite mit einem Feuer-und-vergessen-Effekt erstellen möchten, werden Sie wahrscheinlich nach einiger Zeit in ernsthaften Speicherproblemen enden.

Also, Ihr einziger Test kann nicht schlüssig sein. Sie müssen mit vielen verschiedenen Arten von asynchronen Ereignisdelegaten umgehen, um Ihre Frage richtig zu behandeln.

+0

Sie haben wahrscheinlich Recht, dass einige asynchrone Operationen tatsächlich einen Aufruf von EndInvoke erfordern, ich sprach speziell über Delegate.BeginInvoke. –

1

Betrachten Sie das folgende Beispiel, das auf meinem Computer für ein paar Minuten ausgeführt wurde und einen Arbeitssatz von 3,5 GB erreichte, bevor ich mich entschied, es zu töten.

Action a = delegate { throw new InvalidOperationException(); }; 
while (true) 
    a.BeginInvoke(null, null); 

HINWEIS: Stellen Sie sicher, dass es ohne einen Debugger angeschlossen laufen oder mit „Pause auf Ausnahme geworfen“ und „bricht auf benutzer nicht behandelte Ausnahme“ deaktiviert.

EDIT: Wie Jeff hervorhebt, ist das Speicherproblem hier kein Leck, sondern einfach ein Fall von überwältigendem System durch Warteschlangen arbeiten schneller als es verarbeitet werden kann. In der Tat kann das gleiche Verhalten beobachtet werden, indem der Wurf durch irgendeine geeignet lange Operation ersetzt wird. Und die Speicherauslastung ist beschränkt, wenn zwischen BeginInvoke-Aufrufen genügend Zeit verbleibt.

Technisch lässt das die ursprüngliche Frage unbeantwortet. Unabhängig davon, ob ein Leck auftreten kann oder nicht, ist es jedoch keine gute Idee, Delegate.EndInvoke aufzurufen, da dies dazu führen kann, dass Ausnahmen ignoriert werden.

+1

Interessant, aber haben Sie das versucht? Aktion a = delegieren {neue werfen InvalidOperationException(); }; while (true) a.BeginInvoke (ar => { versuchen { a.EndInvoke (ar); } catch {} }, null); Die Speicherbelegung sieht beim Aufrufen von EndInvoke gleich aus, der Speicher wird wahrscheinlich nur dann hoch, wenn mehr Arbeitsaufgaben in der Warteschlange stehen, als sie beim Auslösen einer Ausnahme ausgeführt werden können. –

+0

Sie haben Recht. Ich habe meine Antwort korrigiert. –

11

Ob es zur Zeit leckt etwas Speicher ist nicht Sie abhängen sollte . Das Framework-Team könnte in Zukunft Dinge auf eine Art ändern, die ein Leck verursachen könnte, und da die offizielle Richtlinie lautet "Sie müssen EndInvoke aufrufen", dann ist es "von Entwurf".

Möchten Sie wirklich die Chance nutzen, dass Ihre App irgendwann in der Zukunft plötzlich Speicher verliert, weil Sie sich auf das beobachtete Verhalten über die dokumentierten Anforderungen verlassen?

+2

+1. Darüber hinaus muss nicht über die Gefahren einer zukünftigen Überarbeitung des Rahmens spekuliert werden. Wie ich in meiner Antwort darauf hingewiesen habe, wird die Möglichkeit des Speicherlecks außer Acht gelassen, wenn "Delegate.EndInvoke" nicht aufgerufen wird, werden Ausnahmen ignoriert und Sie können nicht wissen, ob die Aktion erfolgreich abgeschlossen wurde oder nicht. –

Verwandte Themen