IEnumerableIDisposable: Wer verfügt über was und wann - Habe ich es richtig verstanden?

8

Hier ist ein hypothetisches Szenario.

Ich habe sehr viele Benutzernamen (sagen wir 10.000.000.000.000.000.000.000. Ja, wir sind im intergalaktischen Zeitalter :)). Jeder Benutzer hat seine eigene Datenbank. Ich muss die Liste der Benutzer durchlaufen und einige SQL für jede der Datenbanken ausführen und die Ergebnisse ausdrucken.

Da ich die Güte der funktionalen Programmierung gelernt habe und ich mich mit so vielen Benutzern befasse, entscheide ich mich, dies mit F # und reinen Sequenzen zu implementieren (auch bekannt als IEnumerable). Und hier gehe ich.

%Vor%

Schön? Elegant? Absolut.

Aber! Wer und an welchem ​​Punkt verfügt SqlConnections?

Und ich denke nicht die Antwort mapConnectionToResult sollte es tun ist richtig, weil es nichts über die Lebensdauer der Verbindung, die es gegeben ist, weiß. Und je nachdem, wie mapUsersToConnections implementiert ist, und verschiedene andere Faktoren können die Dinge funktionieren oder nicht.

Da mapUsersToConnections der einzige andere Ort ist, der Zugang zu einer Verbindung hat, muss er dafür verantwortlich sein, über SQL Verbindung zu verfügen.

In F # kann dies wie folgt gemacht werden:

%Vor%

Der C # -Auswahlwert wäre:

%Vor%

Die Tests, die ich durchgeführt habe, legen nahe, dass die Objekte korrekt an richtigen Stellen angeordnet werden, selbst wenn sie parallel ausgeführt werden: einmal am Ende der gesamten Iteration für die gemeinsame Verbindung; und nach jedem Iterationszyklus für die nicht geteilte Verbindung.

Also, DIE FRAGE: Habe ich das richtig verstanden?

BEARBEITEN :

  1. Einige Antworten haben auf einige Fehler im Code hingewiesen, und ich habe einige Korrekturen vorgenommen. Das vollständige Arbeitsbeispiel, das kompiliert wird, ist unten.

  2. Die Verwendung von SqlConnection dient nur zum Beispiel, es ist wirklich irgendein IDisposable.

Beispiel, das kompiliert

%Vor%

Die Ergebnisse sind:

Gemeinsame Verbindung: "Hallo" "Hallo" ... "Entsorgen"

Individuelle Verbindungen: "Hallo" "Entsorgen" "Hallo" "Entsorgen"

    
Komrade P. 21.07.2011, 15:51
quelle

8 Antworten

7

Ich würde diesen Ansatz vermeiden, da die Struktur fehlschlagen würde, wenn ein unwissender Benutzer Ihrer Bibliothek etwas wie

tun würde %Vor%

Ich bin kein Experte für dieses Problem, aber ich nehme an, dass es eine bewährte Methode ist, Sequenzen / IEnumerables nicht mit Dingen zu mucken, nachdem sie ausgegeben wurden, da ein intermediärer ToList () -Aufruf zu unterschiedlichen Ergebnissen führt als nur direkt auf die Sequenz einwirken - DoSomething (GetMyStuff ()) unterscheidet sich von DoSomething (GetMyStuff (). ToList ()).

Warum verwenden Sie nicht einfach Sequenzausdrücke für das ganze Ding, da dies dieses Problem vollständig umgehen würde:

%Vor%

(where userToCxn und cxnToResult sind beide einfache Eins-zu-eins-Funktionen, die nicht disponieren). Dies scheint lesbarer als alles andere und sollte zu den gewünschten Ergebnissen führen, ist parallelisierbar und funktioniert für jeden Wegwerfartikel. Dies kann mit folgender Technik in C # LINQ übersetzt werden: Ссылка

%Vor%

Eine andere Lösung wäre, die Funktion "getSomethingForAUserAndDisposeTheResource" zuerst zu definieren und dann als grundlegenden Baustein zu verwenden:

%Vor%

Sobald Sie dies haben, können Sie von dort aus leicht aufbauen:

%Vor%

Auf diese Weise sind Sie nicht verpflichtet, die gesamte Sequenz abzurufen, wenn Sie nur einen Benutzer haben möchten. Ich denke, die Lektion, die hier gelernt wurde, macht Ihre Kernfunktionen einfach und das macht es einfacher, Abstraktionen darüber zu bauen. Und beachte, dass keine dieser Optionen fehlschlägt, wenn du irgendwo in der Mitte eine ToList machst.

    
Dax Fohl 21.07.2011, 22:04
quelle
4
%Vor%

Das sieht für mich nicht richtig aus - Sie würden über eine Verbindung verfügen, sobald eine neue Verbindung vom nächsten Benutzer angefordert wird (der nächste Iterationszyklus) - das heißt, diese Verbindungen können nur nacheinander verwendet werden - Sobald Benutzer B mit seiner Verbindung arbeitet, wird die Verbindung von Benutzer A entsorgt. Ist das wirklich was du willst?

    
BrokenGlass 21.07.2011 15:57
quelle
4

Ihr F # -Sample führt keine Typüberprüfung durch (auch wenn Sie Ihren Funktionen eine Dummy-Implementierung hinzufügen, z. B. mit failwith ). Ich nehme an, dass Ihre Funktionen userToConnection und connectionToResult tatsächlich einen Benutzer zu eins Verbindung zu eins Ergebnis nehmen. (Anstatt mit Sequenzen wie in Ihrem Beispiel zu arbeiten):

%Vor%

Wenn Sie nun die Verbindung privat zu userToConnection weiterverwenden möchten, können Sie sie so ändern, dass sie keine Verbindung SqlConnection zurückgibt. Stattdessen kann es eine Funktion höherer Ordnung zurückgeben, die eine Verbindung zu einer Funktion herstellt (die im nächsten Schritt spezifiziert wird) und nach dem Aufruf der Funktion die Verbindung aufhebt. Etwas wie:

%Vor%

Sie können currying verwenden, wenn Sie userToConnection user schreiben, erhalten Sie eine Funktion, die eine Funktion erwartet und das Ergebnis zurückgibt: (SqlConnection -> 'R) -> 'R . Dann können Sie Ihre Gesamtfunktion wie folgt zusammenstellen:

%Vor%

Ich bin mir nicht ganz sicher, ob Sie einen einzelnen Benutzer einer einzelnen Verbindung zuordnen möchten, aber Sie könnten dasselbe Prinzip (mit wiederkehrenden Funktionen) verwenden, selbst wenn Sie mit Sammlungen von Sammlungen arbeiten.

    
Tomas Petricek 21.07.2011 16:17
quelle
3

Ich denke, darin liegt ein großer Raum für Verbesserungen. Es sieht nicht so aus, als müsste der Code kompiliert werden, da mapUserToConnection eine Sequenz zurückgibt und mapConnectionToResult eine Verbindung akzeptiert (wenn Sie Ihre map s auf collect s ändern, wird dies behoben).

Ich bin mir nicht sicher, ob ein Benutzer zu mehreren Verbindungen zugeordnet werden soll oder ob es eine Verbindung pro Benutzer gibt. Im letzteren Fall scheint es übertrieben, für jeden Benutzer eine Singleton-Sequenz zurückzugeben.

Im Allgemeinen ist es eine schlechte Idee, ein IDisposable von einer Sequenz zurückzugeben, da Sie nicht kontrollieren können, wann das Objekt entsorgt wird. Ein besserer Ansatz besteht darin, den Umfang von IDisposable auf eine Funktion zu beschränken. Diese "controlling" -Funktion könnte einen Rückruf akzeptieren, der die Ressource verwendet, und nachdem der Rückruf ausgelöst wurde, könnte er die Ressource entsorgen (die using Funktion ist ein Beispiel dafür. In Ihrem Fall könnte die Kombination von mapUserToConnection und mapConnectionToResult das Problem vollständig vermeiden, da die Funktion die Lebensdauer der Verbindung steuern könnte.

Sie würden mit so etwas enden:

%Vor%

Dabei steht mapUserToResult für string -> string (akzeptiert einen Benutzer und gibt ein Ergebnis zurück, wodurch die Lebensdauer jeder Verbindung gesteuert wird).

    
Daniel 21.07.2011 16:14
quelle
1

Das sieht für mich nicht ganz richtig aus - warum geben Sie beispielsweise eine Abfolge von Verbindungen für einen einzelnen Benutzernamen zurück? Wollte Ihre Signatur nicht so aussehen (geschrieben als eine Erweiterungsmethode für Linq-ness):

%Vor%

Anayway, geht weiter - im ersten Beispiel:

%Vor%

Dies funktioniert, aber nur , wenn die gesamte Auflistung aufgelistet ist. Wenn (zum Beispiel) nur das erste Element durchlaufen wird, wird die Verbindung nicht entsorgt (zumindest in C #), siehe Yield und Usings - Ihr Dispose darf nicht aufgerufen werden! .

Das zweite Beispiel scheint für mich in Ordnung zu sein, aber ich hatte Probleme mit Code, der eine ähnliche Sache gemacht hat und dazu geführt hat, dass Verbindungen entsorgt wurden, wenn sie es nicht haben sollten.

Allgemein gesprochen habe ich festgestellt, dass die Kombination von dispose und yield return ein kniffliges Geschäft ist und ich tendiere dazu, es zugunsten der Implementierung eines eigenen Enumerators zu vermeiden, der sowohl IDisposable als auch IEnumerable explizit implementiert. So können Sie sicher sein, wann Objekte entsorgt werden.

    
Justin 21.07.2011 16:12
quelle
1

Dispose sollte von jedem aufgerufen werden, der garantieren kann das Objekt wird nicht mehr benutzt. Wenn Sie diese Garantie übernehmen können (sagen Sie, das einzige Mal, wenn das Objekt innerhalb Ihrer Methode verwendet wird), ist es Ihre Aufgabe, es zu entsorgen. Wenn Sie nicht garantieren können, dass das Objekt fertig ist (sagen Sie, dass Sie einen Iterator mit den Objekten offen legen), dann ist es Ihre Aufgabe, sich nicht darum zu kümmern und sich damit befassen zu müssen.

Bei einer potenziellen Designentscheidung können Sie verfolgen, was die CLR für Stream -Instanzen tut. Viele Konstruktoren, die außer Stream akzeptieren, akzeptieren auch bool . Wenn diese bool wahr ist, dann weiß das Objekt, dass es verantwortlich ist, die Stream zu entsorgen, sobald es damit fertig ist. Wenn Sie einen Iterator zurückgeben, können Sie stattdessen tuple vom Typ Disposable,bool zurückgeben.

Ich würde jedoch das eigentliche Problem genauer betrachten. Vielleicht müssen Sie, anstatt sich über solche Dinge zu sorgen, Ihre Architektur ändern, um diese Probleme zu vermeiden. Zum Beispiel, anstatt eine Datenbank pro Benutzer haben eine Datenbank. Oder vielleicht müssen Sie Verbindungspooling verwenden, um die Last von Live-, aber inaktiven Verbindungen zu reduzieren (ich bin nicht 100% über diese letzte Forschung über solche Optionen).

    
Guvante 21.07.2011 17:09
quelle
0

Wenn wir versuchen, dieses Problem nur mit funktionellen Konstrukten zu lösen, ist IMO ein gutes Beispiel für einen F # -Notfall. Rein funktionale Sprachen verwenden normalerweise unveränderbare Datenstrukturen. F #, das auf .NET basiert, tut dies oft nicht, was manchmal sehr gut für Dinge wie Leistung sein kann.

Meine Lösung für dieses Problem besteht darin, das imperative Bit des Erstellens und Zerstörens des Objekts SqlConnection in seiner eigenen Funktion zu isolieren. In diesem Fall verwenden wir useUserConnection dafür:

%Vor%     
Tristan St-Cyr 21.07.2011 18:12
quelle
0

Ich glaube, dass es hier ein Designproblem gibt. Wenn Sie sich die Problembeschreibung ansehen, geht es darum, Informationen über einen Benutzer zu erhalten. Der Benutzer wird als String dargestellt und die Information wird ebenfalls als String dargestellt. Was wir brauchen, ist eine Funktion wie:

%Vor%

Die Verwendung ist so einfach wie:

%Vor%

Nun, wie diese Funktion die Benutzerinformationen abruft, hängt von dieser Funktion ab, ob sie SqlConnection, Dateistrom oder irgendein anderes Objekt verwendet, das wegwerfbar sein kann oder nicht, diese Funktion hat die Verantwortung, die Verbindung zu erstellen und die Ressourcen richtig zu behandeln. In Ihrem Code haben Sie die Verbindungserstellung und das Abrufen von Informationsteilen vollständig getrennt, was zu Verwirrung darüber führt, wer über die Verbindung verfügt.

Wenn Sie nun eine einzelne Verbindung für alle getUserInfo-Methoden verwenden möchten, können Sie diese Methode als

definieren %Vor%

Nun nimmt diese Funktion eine Verbindung (oder kann ein anderes verfügbares Objekt nehmen). In diesem Fall wird diese Funktion nicht vom Verbindungsobjekt getrennt, sondern der Aufrufer dieser Funktion wird es abschalten. Wir können das verwenden wie:

%Vor%

All dies macht deutlich, wer mit den Ressourcen umgeht.

    
Ankur 22.07.2011 06:17
quelle