none
WPF Funktion abbrechen mit Button/ BackgroundWorker RRS feed

  • Frage

  • Hallo zusammen,

    leider komme ich mit meinem Problem selbst nicht weiter und hoffe auf Eure Hilfe.

    Mein Programm nimmt von der WPF-Oberfläche einige Parameter an und startet dann mit Klick auf den Button eine Berechnung. Diese kann bei ungünstig gewählten Parametern lange dauern, daher soll es eine Möglichkeit zum Abbrechen geben. Die bisher erzielten Ergebnisse werden verworfen.

          private void StartClick(object sender, RoutedEventArgs e)
            {
     int pkFrom =0;
    // weitere Parameter
           try
                {
                     pkFrom = Convert.ToInt32(PKFromTextBox.Text);
    // weitere Parameter
    
    // Funktion zur Berechnung
    calculateResult(pkFrom);// und weitere Parameter
    }
    catch(Exception){}
    }

    Entweder bei einem weiteren Klick auf den Start-Button( dann mit Abbruch beschriftet) oder mit Klick auf einen extra Abbruch-Button soll calculateResult() abgebrochen werden. Einfach nur einen Thread zu initialisieren und innerhalb calculateResult() aufrufen, führt zu einem Thread-Fehler. Mit dem BackgroundWorker bin ich eher verwirrt. Wo muss ich den initialisieren? Wo aufrufen? Was kommt nach doWork()? 

    Herzlichen Dank im Voraus

    Ottilie Steinhauer

    Dienstag, 10. September 2013 10:13

Antworten

  • Hallo,
    du erhälst diese Exception, da du auf ein Control zugreifst (PKFromTextBox) das im Besitz des UI-Threads ist. Da der Wert aus der TextBox nicht dynamisch ist, solltest du deren Text als Parameter an den Thread übergeben. Somit erspart sich der Zugriff auf das Control, und du erhälst keine Exception mehr.

    Viele Grüße Holger M. Rößler

    • Als Antwort vorgeschlagen Holger M. Rößler Freitag, 13. September 2013 19:44
    • Als Antwort markiert ottilie Mittwoch, 2. Oktober 2013 10:32
    Mittwoch, 11. September 2013 10:54
  • Hallo Ottilie,

    hier ein Beispiel von vielen Möglichkeiten ;-):

    private async void CalculateButton_Click(object sender, RoutedEventArgs e)
    {
        int pk;
    
        if (!Int32.TryParse(TextBox.Text, out pk)) return;
    
        CalculateButton.IsEnabled = false;
        //mit parameter übergabe
        await Calculate(pk);
    
        //ohne parameter
        await Calculate();
        CalculateButton.IsEnabled = true;
    }
    
    private Task Calculate(int pk)
    {
        return Task.Run(() =>
            {
                //hier findet dein langlaufender prozess statt
            });
    }
    
    private Task Calculate()
    {
        return Task.Run(() =>
        {
            var pkx = 0;
            // aus einer TextBox lesen
            Dispatcher.BeginInvoke(new Action(() => Int32.TryParse(TextBox.Text, out pkx)));
    
            //in eine ListBox schreiben
            Dispatcher.BeginInvoke(new Action(() => ListBox.Items.Add(pkx)));
        });
    }

    Wie immer gibt es noch einige weitere Möglichkeiten, insbesondere... solche Threads auch abzubrechen... es ist aber recht schwierig brauchbare Beispiele zu zeigen, wenn man nicht weiß was in den Prozessen ausgeführt wird. Wenn du also weitere Unterstützung brauchst wäre es besser etwas mehr Code zu posten und, wenn möglich, einfach mal beschreibst was du vor hast, vielleicht gibt es auch andere Lösungsansätze ;-).

    Wenn du Collection's füllen willst gibt es auch noch ganz andere, sehr elegante, Möglichkeiten... die erfordern jedoch das 4.5er .NET-Framework

    Und bevor ich es vergesse... sorge dafür, dass dein Button nur einmal geklickt werden kann sonst führt das zu unkontrollierbaren Ergebnissen ;-). Es sei denn du willst dass man öfter drauf klicken kann, dann muss das Threading anders implementiert werden.

    Viele Grüße Carl


    Carl-Christian Schaffert Bahnhofsweg 2 82008 Unterhaching mobile: 0152-33 72 79 49 email: carl-christian.schaffert@dotnet-dev-expert.de skype: carl.christian.schaffert


    Sonntag, 15. September 2013 20:55

Alle Antworten

  • Hey,

    mein Vorschlag wäre einen eigenen Tread zu verwenden, der vom Dispatcher also UI Thread gesteuert wird. Somit kannst du diesen Tread jedezeit killen bzw. wenn er fertig ist mit berechnen, seinen Resultat ins UI Thread hinüberführen.


    Dienstag, 10. September 2013 11:26
  • Hallo,
    hast du die "WorkerSupportsCancellation" Property des BackgroundWorkers auf true gesetzt? Der steht per default auf false und somit unterstützt der Worker keinen Abbruch.

    Wenn du die Property auf true gesetzt hast, muss du innerhalb deiner "DoWork" Methode auf die Property "CancellationPending" prüfen. Ist diese true, so wurde von aussen ein Abbruch angefordert, und du kannst den Worker abbrechen.

    Ich hoffe, das hilft dir weiter...


    Viele Grüße Holger M. Rößler

    Dienstag, 10. September 2013 13:26
  • Hallo,

    so weit war ich schon.

          private void StartClick(object sender, RoutedEventArgs e)
            {
     int pkFrom =0;
    // weitere Parameter
    Thread thread = new Thread(delegate(){
           try
                {
                     pkFrom = Convert.ToInt32(PKFromTextBox.Text);
    // weitere Parameter
    
    // Funktion zur Berechnung
    calculateResult(pkFrom);// und weitere Parameter
    }
    catch(Exception){}
    }
           });
                thread.Start();
    }

    führt zur Fehlermeldung " Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da sich das Objekt im Besitz eines anderen Threads befindet." in der Zeile "pkFrom = Convert.ToInt32(PKFromTextBox.Text);" also dort, wo die Parameter eingelesen werden sollen. Dieselbe Fehlermeldung, wenn auch an anderer Stelle kommt, falls ich nur die Funktion zur Berechnung in den Thread nehme, da auch diese Funktion die Oberfläche manipuliert.

    Wo muss ich den Thread hintun? Bzw. was muss noch dazu?

    Gruß

    Ottilie Steinhauer

    Mittwoch, 11. September 2013 07:36
  • Hallo,
    du erhälst diese Exception, da du auf ein Control zugreifst (PKFromTextBox) das im Besitz des UI-Threads ist. Da der Wert aus der TextBox nicht dynamisch ist, solltest du deren Text als Parameter an den Thread übergeben. Somit erspart sich der Zugriff auf das Control, und du erhälst keine Exception mehr.

    Viele Grüße Holger M. Rößler

    • Als Antwort vorgeschlagen Holger M. Rößler Freitag, 13. September 2013 19:44
    • Als Antwort markiert ottilie Mittwoch, 2. Oktober 2013 10:32
    Mittwoch, 11. September 2013 10:54
  • Hallo Ottilie,

    hier ein Beispiel von vielen Möglichkeiten ;-):

    private async void CalculateButton_Click(object sender, RoutedEventArgs e)
    {
        int pk;
    
        if (!Int32.TryParse(TextBox.Text, out pk)) return;
    
        CalculateButton.IsEnabled = false;
        //mit parameter übergabe
        await Calculate(pk);
    
        //ohne parameter
        await Calculate();
        CalculateButton.IsEnabled = true;
    }
    
    private Task Calculate(int pk)
    {
        return Task.Run(() =>
            {
                //hier findet dein langlaufender prozess statt
            });
    }
    
    private Task Calculate()
    {
        return Task.Run(() =>
        {
            var pkx = 0;
            // aus einer TextBox lesen
            Dispatcher.BeginInvoke(new Action(() => Int32.TryParse(TextBox.Text, out pkx)));
    
            //in eine ListBox schreiben
            Dispatcher.BeginInvoke(new Action(() => ListBox.Items.Add(pkx)));
        });
    }

    Wie immer gibt es noch einige weitere Möglichkeiten, insbesondere... solche Threads auch abzubrechen... es ist aber recht schwierig brauchbare Beispiele zu zeigen, wenn man nicht weiß was in den Prozessen ausgeführt wird. Wenn du also weitere Unterstützung brauchst wäre es besser etwas mehr Code zu posten und, wenn möglich, einfach mal beschreibst was du vor hast, vielleicht gibt es auch andere Lösungsansätze ;-).

    Wenn du Collection's füllen willst gibt es auch noch ganz andere, sehr elegante, Möglichkeiten... die erfordern jedoch das 4.5er .NET-Framework

    Und bevor ich es vergesse... sorge dafür, dass dein Button nur einmal geklickt werden kann sonst führt das zu unkontrollierbaren Ergebnissen ;-). Es sei denn du willst dass man öfter drauf klicken kann, dann muss das Threading anders implementiert werden.

    Viele Grüße Carl


    Carl-Christian Schaffert Bahnhofsweg 2 82008 Unterhaching mobile: 0152-33 72 79 49 email: carl-christian.schaffert@dotnet-dev-expert.de skype: carl.christian.schaffert


    Sonntag, 15. September 2013 20:55
  • Hallo Carl,

    Vielen Dank für Deine Hilfe, genau das war es wonach ich gesucht habe. Entschuldige die späte Antwort, es gab noch ein paar dringendere Bugs.

    Aber gleich habe ich noch weitere Fragen, das Threading ist ja kein Selbstzweck. Ich habe den von Dir vorgeschlagenen Code verwendet:

    private Task Calculate(int pk)
    {
        return Task.Run(() =>
            {
                //hier findet dein langlaufender prozess statt
            });
    }
    

    Wenn jetzt der Thread läuft, soll er auch von außen abgebrochen werden können mit Button. Wie am besten? 

    Am liebsten würde ich den vorhandenen Button auch als Abort-Button benutzen, etwa so

    if (kein Task) Task starten s.o.

    else Task abbrechen

    Wie stelle ich das am besten an?

    Gruß

    Ottilie

    Mittwoch, 2. Oktober 2013 10:41