Laden...

Abbrechen von Tasks

Erstellt von Butzi74 vor einem Jahr Letzter Beitrag vor einem Jahr 548 Views
B
Butzi74 Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr
Abbrechen von Tasks

Hallo zusammen.
Vorweg: Ich bin absoluter Neuanfänger!! 😦
Mein aktuelles Vorhaben: In C# WPF ein Tool erstellen, mit dem User seine Daten sichern und auch wieder zurück sichern kann. Er startet das Tool, klickt z.b. auf SICHERN und wird dann gefragt, wohin gesichert werden soll. Das Tool erstellt dann dort einen Ordner (Bestehend aus aktuellem Datum, dem Usernamen und dem Rechnernamen) und kopiert dort die kompletten Ordner Desktop, Dokumente, Downloads, Bilder etc..

Soweit funktioniert das auch schon bei mir prima. Habe mir alles so aus dem Internet zusammengesucht und solange herumgebastelt bis es geht🙂
Nur bei einer Sache komme ich einfach nicht weiter. Wenn der User während des Kopierens aus ABBRECHEN klickt, soll der aktuell laufende Task abgebrochen werden.

So ist mein Aufbau:
MainWindow mit einem Frame. Dort wird als erstes ein Frame namens START geladen. Klickt der User auf WEITER wird dieses Frame durch die Seite (Page) "Übersicht" ersetzt. Dort kann der User auswählen zwischen SICHERN und ZURÜCKSICHERN. Klickt er z.b. auf SICHERN, wird in das Frame die Seite (Page) "Sichern geladen wo auch dann gleich mein Programm gestartet wird.

Da ich parallel beim Kopieren eine Art Status haben wollte (Also dass gerade z.b. der Ordner Desktop kopiert wird), verwende ich async und await.
Wie gesagt. Geht bisher alles prima. Nur bekomme ich dieses Abbrechen nicht hin:-(
So rufe ich z.b. die Kopierfunktion für Desktop auf und gefolgt von Kopieren für Dokumente


#region Dateien kopieren
                Desktopbox.Visibility = Visibility.Visible;
                await Task.Run(() =>
                {
                    Kopier Desktop = new Kopier();
                    if (Desktop.Abbruch == true)
                        return;
                    Desktop.Kopieren(Quelle + @"\Desktop", pathString + @"\");
                });
                Desktopkreis.Visibility = Visibility.Hidden;
                Desktophaken.Visibility = Visibility = Visibility;


                Dokumentebox.Visibility = Visibility.Visible;
                await Task.Run(() =>
                {
                    Kopier Dokumente = new Kopier();
                    if (Dokumente.Abbruch == true)
                        return;
                    Dokumente.Kopieren(Quelle + @"\Documents", pathString + @"\");
                });
                Dokumentekreis.Visibility = Visibility.Hidden;
                Dokumentehaken.Visibility = Visibility = Visibility;


Als Funktion für das Kopieren von Ordner mit Unterordner verwende ich dieses hier, was ich als CLASS KOPIER eingebaut habe.


class Kopier
    {
        public Boolean Abbruch = false;

        #region Kopierfunktion
        public void Kopieren(string sourceDir, string destinationDir)
        {
            if (Abbruch == true)
                return;
            // alle Unterverzeichnisse des aktuellen Verzeichnisses ermitteln
            string[] subDirectories = Directory.GetDirectories(sourceDir);
            // Zielpfad erzeugen
            StringBuilder newTargetPath = new StringBuilder();
            {
                newTargetPath.Append(destinationDir);
                newTargetPath.Append(sourceDir.Substring(sourceDir.LastIndexOf(@"\")));
            }
            // wenn aktueller Ordner nicht existiert -> erstellen
            if (!Directory.Exists(newTargetPath.ToString()))
                Directory.CreateDirectory(newTargetPath.ToString());
            // Unterverzeichnise durchlaufen und Funktion mit dazu gehörigen Zielpfad erneut aufrufen (Rekursion)
            foreach (string subDirectory in subDirectories)
            {
                try
                {
                    string newDirectoryPath = subDirectory;
                    // wenn ''/'' an letzter Stelle dann entfernen
                    if (newDirectoryPath.LastIndexOf(@"\") == (newDirectoryPath.Length - 1))
                        newDirectoryPath = newDirectoryPath.Substring(0, newDirectoryPath.Length - 1);
                    // rekursiver Aufruf
                    Kopieren(newDirectoryPath, newTargetPath.ToString());
                }
                // Falls Schreibgeschützer Ordner, dann fortfahren
                catch (UnauthorizedAccessException)
                {
                    continue;
                }
            }
            // alle enthaltenden Dateien des aktuellen Verzeichnisses ermitteln
            string[] fileNames = Directory.GetFiles(sourceDir);
            foreach (string fileSource in fileNames)
            {
                // Zielpfad + Dateiname
                StringBuilder fileTarget = new StringBuilder();
                {
                    fileTarget.Append(newTargetPath);
                    fileTarget.Append(fileSource.Substring(fileSource.LastIndexOf(@"\")));
                }
                // Datei kopieren, wenn schon vorhanden dann überschreiben

                try
                {
                    File.Copy(fileSource, fileTarget.ToString(), true);
                }
                catch (IOException)
                {
                    continue;
                }
            }
        }
        #endregion Kopierfunktion
    }

Mir ist durchaus bewusst das ich hier manches total blödsinnig programmiert habe oder eher fusch ist. Ich lebe eben nach dem Motto Learning by doing🙂

B
Butzi74 Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

HI und vielen Dank. Aber das ist mir dann doch zu hoch irgendwie:-(

Im Endeffekt muss ich bei Klick auf ABBRECHEN erwirken, dass der Task (den ich mit diesem AWAIT quasi starte, abgebrochen wird. Parallel muss aber auch das Skript slebst abgebrochen werden, da mir sonst das nächste AWAIt bzw. dessen Task ausführt.

Wie gesagt: Absoluter Neuanfänger. mache C# seid gerade mal 1 Woche und dies nebenbei quasi.:-(

4.939 Beiträge seit 2008
vor einem Jahr

Es ist so ähnlich, wie du die Variable Abbruch gedacht hast, nur daß es thread- bzw. tasksafe ist. Du mußt nur in deinen Kopierschleifen den CancellationToken.IsCancellationRequested-Status überprüfen und dann ThrowIfCancellationRequested() aufrufen.

Lege dazu CancellationTokenSource als Membervariable an und rufe über das "Abbrechen"-Command dann dafür Cancel() auf (diese Variable mußt du dann noch als Parameter zusätzlich der Kopieren-Methode übergeben, damit diese darauf zugreifen kann).

PS: Dateipfade solltest du mit der Path.Combine-Methode zusammensetzen.

2.079 Beiträge seit 2012
vor einem Jahr

ThrowIfCancellationRequested reicht auch direkt, wirft ja nur, wenn IsCancellationRequested = true ist, man spart sich also ein if.

Heißt:


void MyMethod(CancellationToken cancellationToken)
{
    while (/* while condition */)
    {
        cancellationToken.ThrowIfCancellationRequested();

        // Do work ...
    }
}

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

4.939 Beiträge seit 2008
vor einem Jahr

Das hatte ich nur explizit hingeschrieben, damit Butzi74 den zugehörigen Code aus dem Artikel versteht.

2.079 Beiträge seit 2012
vor einem Jahr

Tatsache, da wird vorher geprüft - gut, ich hab nix gesagt 🙂

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

B
Butzi74 Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Vielen Dank an dieser Stelle bis hierher. Ich versuche dann mal dies zu verstehen und entsprechend einzubauen. Wo auch immer und wie🙂 Egal wie, melde ich mich dann wieder.

B
Butzi74 Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Ok, ich habe nun ewig herumgemacht und bekomme es einfach nicht hin 😦
Eine freche Frage jetzt mal, aber könnte mir jemand dies als Code zeigen, wie es bei mir aussehen müsste? Nein, ich möchte nicht dass jemand meine Arbeite nun macht aber ich bin einer derjenigen der es eher durch Sehen versteht. Wenn ihr versteht wie ich es meine?

Ich kann dazu gerne mal den kompletten Code dieser Page hier hochladen mit der Bitte aber nichts sonst noch zu ändern oder ähnliches. Das möchte ich gerne selbst hinbekommen. Nur bei diesem Unterbrechen bin ich nun echt am Ende und verliere jegliche Lust an diesem Projekt weiter zu machen. Dabei bin ich schon fast fertig:-( Also für meine Verhältnisse zumindest- Klar, der beste und sauberste Code ist es bei weitem nicht. Aber für gerade Mal eine Woche C# WPF und dies nur 1-2 Stunden Abends bin ich doch ganz stolz drauf wie weit ich es hinbekommen habe.

Doch dieses blöde Abbrechen....ahhh.

Also ich nur eine Frage ob mir dies anhand meines Codes zeigen könnte wie es aussehen müsste. Wenn nicht, ist das vollkommen ok.

B
Butzi74 Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Ähmm, ich habe jetzt einfach mal direkt die Seite (wo das ganze Skript drin ist) aus dem Projektordner kopiert.

B
Butzi74 Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Hier die XAML Datei dazu.

Wie gesagt...das ist eine PAGE, die ich in ein Frame lade.

4.939 Beiträge seit 2008
vor einem Jahr

OK, geänderter Code im Anhang.
Meine Änderungen habe ich mit // by Th69 kommentiert.

Du mußt jetzt noch um deine ganzen Kopiermethoden in der Go-Methode noch ein try ... catch (OperationCanceledException) setzen, damit diese Exception gefangen wird und alle weiteren Kopiermethoden nicht mehr aufgerufen werden (ich habe dies extra nicht gemacht, um deinen Code nicht weiter zu ändern, wie du ja auch geschrieben hast 😉.

Bedenke außerdem, daß beim Kopieren einer großen Datei auch erst nach Abschluß der Kopiervorgang unterbrochen wird (ansonsten müßtest du selber das Dateikopieren implementieren und dort dann für jeden Dateiblock token.ThrowIfCancellationRequested() aufrufen).

Aber noch ein paar sonstige Anmerkungen zu deinem Code:

  • Niemals Pfad-Literale (wie z.B. "C:\Users\" oder @"\Desktop") im Code benutzen, sondern immer Environment.GetFolderPath benutzen (die Pfade können je nach System immer unterschiedlich sein)!
  • Du erstellst immer wieder (unnötigerweise) neue Kopier-Objekte - da die Kopieren-Methode keine Instanzmember benutzt, sollte sie static sein (sollte auch von der CodeAnalyse als Warnung angezeigt werden).
  • Du benutzt zwar schon teilweise die Path-Methoden, aber nicht konsequent. Alle Funktionalität bzgl. @"\" solltest du mit Path-Methoden programmieren.
    (Wegen der using-Anweisung brauchst du nicht immer System.IO. vor Benutzung zu schreiben)
  • Du hast mehrfach Desktophaken.Visibility = Visibility = Visibility; im Code stehen??? Und auch Desktopbox.Visibility sowie Desktopkreis.Visibility setzt du unnötigerweise jedesmal wieder beim Kopieren eines Verzeichnisses.

Viel Erfolg noch!

PS: Die Sichern.xaml scheint dieselbe Datei wie Sichern.xaml.cs zu sein!?!

B
Butzi74 Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Guten Morgen.

Vielen vielen Dank. Ich werde es in der nächsten Pause mir anschauen und probieren. Und dann mir auch genau mal ansehen wie das nun funktioniert.
Auch habe ich mir nun deine genannten Punkte mal aufgeschrieben, dass ich mir diese mal anschaue.Damit meine ich das Thema Environment.GetFolderPath.

Auch das mit dem Desktophaken.Visibility = Visibility = Visibility; will ich noch sauberer lösen. Ich habe, damit der User sieht dass überhaupt was passiert, pro Kopiervorgang einen Text (Beispiel "Kopiere nun Ordner Desktop.... :" sowie eine Prozessbar und einen "FERTIG Haken". Während des Kopierens ist der Text und die Prozessbar eingeblendet bzw. wird zu Begin beim Kopieren eingeblendet. Wenn der Kopiervorgang zu Ende ist, wird die Prozessbar ausgeblendet und statt dessen der Fertig Haken eingeblendet. Da war ich auch mal kurz dabei das ganze in eine eigene Funktion zu bauen die ich dann z.b. mit "Kopierinfoonn(Desktop)" oder "Kopierinfofertig(Desktop)" lösen wollte. Doch bei der Übergabe des jeweiligen Namens (hier Beispiel "Desktop") und dann daras den jeweiligen Textboxnamen zu generieren, scheiterte ich.:-( Ich meine damit: Der Name/String DESKTOP wurde zwar an die Funktion übergeben, aber ich konnte daraus dann z.b. nicht "Desktopbox" erstellen und damit dann "Desktopbox.Visibility" erstellen.

Auch habe ich vor, die Kopierfunktion "Class Kopier" in eine extra Klasse zu packen und diese dann aus der Klasse heraus anzusprechen. Am liebsten so, dass meine "Sichern.cs" schon den Befehl "Kopier(Quelle, Ziel)" kennt.

Ja, kleines Projekt aber noch viel vor🙂 Wie gesagt, bin absoluter Neuling und hatte mir das auch alles viel leichter vorgestellt. War schon beim KOpieren der Ordner inkl. Unterordner schockiert was man alles dazu machen/schreiben musste an Code. Bei einer CMD Konsole mache ich eben einfach nur "Xcopy ..... .... /E/D/I/Y" und schon wird der Ordner inkl. Dateien und Unterordner kopiert.

So, jetzt schaue icb mir nachher mal deine Hilfe an. Danke auf jeden Fall nochmal 🙂 Und vorab schönes Wochenende, falls ich doch nicht mehr dazu heute komme.

B
Butzi74 Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Na, wer hätte das gedacht. Doch schon früher dazu gekommen mir das anzusehen. Allerdings bekomme ich da ein paar Fehler rein bzw. meckert mir Visual Studio:-(

Siehe Anhang Screenshot .

Bei Punkt 1. nehme ich an, da nun drei Stringparameter eigentlich übergeben werden müssen, muss dies so heissen?


 Kopieren(newDirectoryPath, newTargetPath.ToString(),token);

Und bei Punkt 2: muss noch das token. davor. Also so:


token.ThrowIfCancellationRequested();

Zumindest meckert mit Visual Studio dann nicht mehr mit roten Wellenlinien an. Aber... zwar wird meine Funktion nun, wie gewünscht, unterbrochen, aber ich bekomme dann immer eine Fehlermeldung von Visual Studio. "Unbehandelte Ausnahme":-(

Oder liegt es daran, das ich try und Catch in meiner GO Methode falsch gesetzt habe?


Desktopbox.Visibility = Visibility.Visible;
            await Task.Run(() =>
            {
                try { 
                Kopier Desktop = new Kopier();
                Desktop.Kopieren(Quelle + @"\Desktop", pathString + @"\", tokenSource.Token);
                }
                catch (UnauthorizedAccessException)
                {
                    return;
                }

            });
            Desktopkreis.Visibility = Visibility.Hidden;
            Desktophaken.Visibility = Visibility = Visibility;


            Dokumentebox.Visibility = Visibility.Visible;
            await Task.Run(() =>
            {
                try
                {
                Kopier Dokumente = new Kopier();

B
Butzi74 Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Vergiss es wieder was die Fehlermeldung angeht🙂 Leider kann man seinen Beitrag hier wohl nicht nachträglich zweimal editieren.
Die Fehlermeldung ist weg da ich einen Fehler drin hatte in der Try Catch Methode. Mein Fehler.

Funktioniert wie gewünscht. Super. Vielen vielen Dank.
Als nächstes werde ich mir nun mal diese Path Geschichte dann anschauen sowie wie ich mein Ein und Ausblenden von der Prozessbar etc. besser hinbekomme.
Am liebsten wäre mir ja, dass er bei jedem Kopiervorgang wirklich anzeigt wie weit er schon ist. Also die Prozessbarwerte sich anpasst und wenn 100% geschafft ist, er dann mir den Fertig haken anzeigt. Vielleicht bekomme ich das auch irgendwann mal hin aber wäre mein letzter Schritt um es NOCH BESSER hin zu bekommen.🙂

Somit nochmal schönes Wochenende vorab und echt herzlichen Dank! 🙂

4.939 Beiträge seit 2008
vor einem Jahr

Sorry wegen den Syntax-Fehlern, aber ich konnte deinen Code ja nicht bei mir testen (sondern habe ihn nur im Editor abgeändert).

Und wegen der Exception: Du mußt ja auch die OperationCanceledException abfangen.
Ich meinte aber nicht explizit bei jedem Kopieren-Aufruf, sondern um alle Aufrufe (also von #region Dateien kopieren bis #endregion Dateien kopieren/#endregion Sicherungsprotokoll erstellen - je nachdem ob und was du im Fall einer Exception in das Sicherungsprotokoll schreiben möchtest).
Wenn diese Exception trotzdem nicht gefangen wird, dann probiere mal, das token auch an die Task.Run-Methode zu übergeben.

Und bei


Desktophaken.Visibility = Visibility = Visibility;

meinte ich zum einen die unnötige zweifache Zuweisung von Visibility als auch daß du auf die Page.Visibility zugreifst (meintest du nicht eher Visibility.Visible?).

Das Auslagern der Kopier-Klasse in eine eigene Datei ist auf jeden Fall zu empfehlen (und wie schon geschrieben, verwende static, dann reduziert sich auch dein Code beim jeweiligen Aufruf zu Kopier.Kopieren(Quelle, Ziel, token) - bei Kopieren(Quelle, Ziel) müßtest du noch eine Hilfsmethode in deiner Sichern-Klasse erzeugen).

PS: XCopy ruft intern auch nur die entsprechenden System-Funktionen zum Kopieren von Verzeichnissen und Dateien auf (je nach Parameter), aber leider gibt es direkt für die XCopy-Funktionailtät keine API.
Es gäbe auch noch FileSystem.CopyDirectory, aber das unterstützt auch kein Abbrechen des Vorgangs.

Edit: Ich habe deinen letzten Beitrag jetzt erst gelesen (habe zu lange für diesen gebraucht).
Um eine Progressbar anzuzeigen, müßtest du vorher alle Ordner und Dateien durchgehen und die Gesamtgröße (oder zumindestens die Anzahl der Dateien) ermitteln und diese dann jeweils beim Kopieren aktualisieren (das ist aber schon fortgeschritten, da du dazu [FAQ] Eigenen Event definieren / Information zu Events (Ereignis/Ereignisse) sowie [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke) benötigst).

Es freut mich für dich, daß es jetzt bei dir funktioniert (und ich dir helfen konnte 😉.

B
Butzi74 Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Das mit dem static ist schon mal geil. Danke für den Tipp. Wieder was dazu gelernt. Rest schaue ich mir morgen oder irgendwann an, wenn ich heute nicht mehr dazu kommen sollte.

PS. Ja, das war mein Fehler den ich meinte,. Hatte das falsche CATCH (xxx) verwendet.:-(