Laden...

Verständnisfrage anonyme Methode innnerhalbe einer Foreach-Schleife

Erstellt von Balaban_S vor 9 Jahren Letzter Beitrag vor 9 Jahren 3.126 Views
Balaban_S Themenstarter:in
194 Beiträge seit 2006
vor 9 Jahren
Verständnisfrage anonyme Methode innnerhalbe einer Foreach-Schleife

Hallo Forum

Ich habe vor kurzem ein Programm erstellt, dessen Funktion es sein sollte alle Verzeichnisse innerhalb eines Verzeichnisses mit dem 7-Zip-CommandLine-Tool zu packen. Für jedes Verzeichnis sollte ein Prozess gestartet werden um Ressourcen zu schonen sollte nichts parallel laufen. So schien es mir Plausibel eine Queue<Action> mit allen verzeichnissen zu erstellen und dann die Queue jeweils mit dem Process.Exited Event von 7-Zip.exe als Trigger abzuarbeiten.

In habe versucht den Code verkürzt darzustellen:


		//QuellVerzeichnis
		private string srcDir;
		//ZielVerzeichnis
		private string destDir;
		//Verzeichnisliste
		private List<String> DirectoryNames = new List<string>();
		//Aktionen
		private Queue<Action> ActionList = null;
		 private void ProcessDirectory()
		 {
            if (!string.IsNullOrWhiteSpace(textBox1.Text)&&!string.IsNullOrWhiteSpace(textBox2.Text))
            {
                srcDir = textBox1.Text;
                destDir = textBox2.Text;
            }
	    else
            {
                 return;
             }
           
            if (Directory.Exists(srcDir)&& Directory.Exists(destDir))
            {                                                           
                DirectoryNames.AddRange(Directory.EnumerateDirectories(srcDir));
		///SIGNIFIKANTER BEREICH BEGINN
                //Argumente
                var args = " a -mx9 -bd \"{1}.7z\" \"{0}\\\" -scswin";
                //Dateiname
				var fname = @"C:\Program Files\7-Zip\7z.exe";
				foreach (var item in DireDirectoryNames)
				{
					ActionList.Enqueue(() => {
                    var tmpstr = item;
                    ProcessStartInfo psinfo = new ProcessStartInfo();
                    psinfo.FileName = fname;
                    psinfo.Arguments = string.Format(args, tmpstr, tmpstr.Replace(srcDir, destDir));
                    Debug.WriteLine(psinfo.Arguments);
                    var p = new Process();
                    p.StartInfo = psinfo;
                    p.Start();
                    p.EnableRaisingEvents = true;
					//SchleifenTrigger
                    p.Exited += (o, j) =>
                    {
						GetNextProc();
                    };
					});
                }
				//Erster Aufruf
				 GetNextProc();
                ///SIGNIFIKANTER BEREICH ENDE
            }
        }
        void GetNextProc()
        {
            if (ActionList.Count > 0)
            {
                ActionList.Dequeue()();
            }
            else
            {
                MessageBox.Show("Finished");
            }
        }

Da ich die ARgument für 7-Zip vorher getestet hatte, war ich mir ziemlich sicher , dass alles reibungslos hätte funktionieren sollen, ich wurde jedoch eines anderen belehrt.

Das Programm lief zwar fehlerfrei durch, ich hatte jedoch am Ende immer nur einen Komprimierten Ordner im Zielverzeichnis und dieses hatte den Namen des letzte Verzeichnisses innerhalb des QUellverzeichnisses. Das hatte ich bisher nicht für möglich gehalten, warum auch?

Nach etwa einer Stunde kam ich durch Probieren auf die Lösung, Ich musste anstatt foreach eine for-schleife nehmen und zwar so:



                //Argumente
                var args = " a -mx9 -bd \"{1}.7z\" \"{0}\\\" -scswin";
                //Dateiname
                var fname = @"C:\Program Files\7-Zip\7z.exe";

for (int i = 0; i <  (int i = 0; i < DirectoryNames.Count; i++).Count; i++)
				var item = DirectoryNames[i];

....

Und dann lief das Programm korekt durch und verrichtete die gewünschte Arbeit.

Wie ihr seht ist das Problem jetzt ja schon gelöst nur nimmt es mich sehr Wunder wie diese Verhalten heisst und wo ich mehr darüber nachlesen kann. Ich war bisher der Meinung dass eine Anonyme Methode das Gleiche ist wie eine normale Methode und dass Variablen innerhalb des scopes neu erstellt werden insbesondere immutable-Typen wie Strings.( ALso warum bleibt die Referenz quasi auf item hängen?

Ich verwende in letzter Zeit des öfteren Queue<Action> ist es nicht Ratsam oder könnt ihr evtl. etwas Anderes für solche Zwecke empfehlen?

Ich hoffe, dass ich mich verständlich ausgedrückt habe.

T
2.219 Beiträge seit 2008
vor 9 Jahren

Ich kann dir nur empfehlen den Code neuzuschreiben.
Das sieht alles sehr unschön und teilweise mit deinem umgebauten Code auch sehr gruselig aus.
Das Problem bei foreach ist, dass item an sich immer die Referenz des aktuellen Objekts hat.
In diesem Fall wohl eben des letzten Objekts.

Da deine Queue erst nach dem füllen abgearbeitet wird, ist in der anonymen Methode dann auch item eben das letzte Objekt in deiner Liste.
Du solltest ggf. deine Verarbeitung umstellen von einer anonymen Methode auf eine richtige Methode und übergibst dort einfach dein Item Objekt.
Dann hast du auch nicht solche komischen Nebenwirkungen und dein Code ist auch klarer.

Aber warum machst du es dir eigentlich nicht einfacher und startest einen extra Thread der einfach die Liste durchgeht und dann deine Ordner packt?
Das dürfte deinen Code auch verständlicher machen und eben das erreichen was du amchen willst.
Hier könntest du z.B. einfach einen Task starten und diesen dann deine Verarbeitung erledigen lassen.
Dann bleibt deine UI auch nicht hängen.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

Balaban_S Themenstarter:in
194 Beiträge seit 2006
vor 9 Jahren

Zuerst mal Danke für die Antwort

Ich kann dir nur empfehlen den Code neuzuschreiben.
Das sieht alles sehr unschön und teilweise mit deinem umgebauten Code auch sehr gruselig aus.

Ist klar, es handelt sich hierbei wie gesagt um die verkürzte version.

Da deine Queue erst nach dem füllen abgearbeitet wird, ist in der anonymen Methode dann auch item eben das letzte Objekt in deiner Liste.
Du solltest ggf. deine Verarbeitung umstellen von einer anonymen Methode auf eine richtige Methode und übergibst dort einfach dein Item Objekt.
Dann hast du auch nicht solche komischen Nebenwirkungen und dein Code ist auch klarer.

ja mit var scheint es zu klappen.

Aber warum machst du es dir eigentlich nicht einfacher und startest einen extra Thread ....

War halt wirklich nur quick and dirty......

Dann bleibt deine UI auch nicht hängen.

Meine UI bleibt nich hängen ich benutze schon Invoke(), sieht man nur hier nicht.

Gruss

4.931 Beiträge seit 2008
vor 9 Jahren

Hallo,

das Stichwort, was du suchst heißt Closure, s.a. [Artikel] Delegaten, anonyme Methoden, Lambda-Ausdrücke & Co. (unter dem gleichnamigen Kapitel "Closures").

Es müßte bei dir also so aussehen:


foreach (var item in DirectoryNames)
{
    var tmpstr = item; // <- temporäre Variable innerhalb des foreach-Blocks
    ActionList.Enqueue(() => {
      // mach etwas mit tmpstr (nicht item benutzen)
    }
}

Balaban_S Themenstarter:in
194 Beiträge seit 2006
vor 9 Jahren

das Stichwort, was du suchst heißt Closure, s.a.
>
(unter dem gleichnamigen Kapitel "Closures").

Dass ist es danke