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