Discussion:
Abbrechen des Backgroundworkers funktioniert nicht wie erfolgt
(zu alt für eine Antwort)
Jan Baer
2008-02-18 19:03:36 UTC
Permalink
Hallo alle zusammen,

ich habe eine WPF Anwendung, bei der ich im Hintergrund über den
Backgroundworker Daten laden, während der Benutzer im Vordergrund an der
Oberfläche schon irgendwelche Eingaben vornehmen kann. Es kann also sein,
das der Benutzer durch eine bestimmte Aktion andere Daten laden will, als
die, die der Backgroundworker gerade lädt.

In der Methode, die das Laden über den Backgroundworker initiert, prüfe ich
mit backgroundWorker.IsBusy, ob der Backgroundworker gerade beschäftigt ist.
Wenn ich dort True zurück bekomme, dann breche ich mit
backgroundWorker.CancelAsync den Ladevorgang ab.

if(backgroundWorker.IsBusy)
{
backgroundWorker.CancelAsync();

while(backgroundWorker.IsBusy)
{
Thread.Sleep(100);
}
}

backgroundWorker.RunWorkerAsync();

Im DoWork-Ereignis frage ich CancellationPending ab und setze dann
eventArgs.Cancel = true.

Im RunWorkerCompleted Ereignis sollte dann eigentlich Cancelled = true sein.

Soweit die Theorie, in der Praxis bleibt der Code in der while-Schleife
hängen. Selbst wenn ich nur einmal den aktuellen Thread (über habe es bis
1000ms versucht) schlafen schicke, damit der Hintergrundthread Zeit bekommt,
abzubrechen, funktioniert das ganze nicht. Obwohl im DoWork-Ereignis
eventArgs.Cancel auf true gesetzt wird, ist im Eventhandler von
RunWorkerCompleted die Eigenschaft Cancelled = false. Nach Thread.Sleep ist
der backgroundWorker auch immer noch IsBusy = true.

Weiß jemand, wo hier der Fehler vergraben ist? Wenn ich Thread.Sleep
aufrufe, dann schicke ich doch den aktullen Thread, also in dem Falle den
Vordergrundthread schlafen. Der Hintergrundhtread sollte doch dadurch
eigentlich die Zeit bekommen, um ordnungsgemäß abzubrechen oder?

Vielen Dank für Eure Hilfe im voraus!

Beste Grüße

Jan
Frank Dzaebel
2008-02-18 20:16:32 UTC
Permalink
Hallo Jan,
Post by Jan Baer
In der Methode, die das Laden über den Backgroundworker initiert, prüfe
ich mit backgroundWorker.IsBusy, ob der Backgroundworker gerade
beschäftigt ist.
Wenn ich dort True zurück bekomme, dann breche ich mit
backgroundWorker.CancelAsync den Ladevorgang ab.
ist denn WorkerSupportsCancellation auf true?

_____________
Hier ein Beispiel:

using System;
using System.Threading;
using System.ComponentModel;

class Program
{
static BackgroundWorker bw;
static void Main()
{
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;

bw.DoWork += bw_DoWork;
bw.ProgressChanged += bw_ProgressChanged;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;

bw.RunWorkerAsync ("Hello to worker");

Console.WriteLine ("Press Enter in the next 5 seconds to cancel");
Console.ReadLine();
if (bw.IsBusy) bw.CancelAsync();
Console.ReadLine();
}

static void bw_DoWork (object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i += 20)
{
if (bw.CancellationPending)
{
e.Cancel = true; return;
}
bw.ReportProgress (i);
Thread.Sleep (1000);
}
e.Result = 123; // This gets passed to RunWorkerCompleted
}

static void bw_RunWorkerCompleted (object sender,
RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
Console.WriteLine ("You cancelled!");
else if (e.Error != null)
Console.WriteLine ("Worker exception: " + e.Error.ToString());
else
Console.WriteLine ("Complete - " + e.Result); // from DoWork
}

static void bw_ProgressChanged (object sender,
ProgressChangedEventArgs e)
{
Console.WriteLine ("Reached " + e.ProgressPercentage + "%");
}
}

[Threading in C# - Part 3 - Using Threads]
http://www.albahari.com/threading/part3.html


ciao Frank
--
Dipl.Inf. Frank Dzaebel [MCP/MVP C#]
http://Dzaebel.NET
Jan Baer
2008-02-19 05:41:25 UTC
Permalink
Hallo Frank,
Post by Frank Dzaebel
ist denn WorkerSupportsCancellation auf true?
ja WorkerSupportsCancellation ist auf true gesetzt, sonst würde ich ja eine
Exception bekommen.

BG Jan
Frank Dzaebel
2008-02-19 09:33:44 UTC
Permalink
Hallo Jan,

Vorab, es liegt dann höchstwahrscheinlich
daran, dass folgendes nicht sauber ist:

while(backgroundWorker.IsBusy)
{ Thread.Sleep(100); }

// neben andere Dingen in Deinem Code!

Hier befindest Du Dich im HauptThread!!
Du blockierst somit alle Events, denn die
MessageLoop wird über den HauptThread
abgearbeitet. Ergo blockierst Du auch
das RunWorkerCompleted-Event des
BackgroundWorker's, ergo ist der ewig "busy" ;-)

Eigentlich brauchst Du die Schleife überhaupt nicht,
denn Du kannst über Events arbeiten, aber *wenn*,
dann müsstest Du die Events zwischendurch
(z.B. nach Thread.Sleep) mal abarbeiten.
Das ginge z.B. so:

while (bw.IsBusy)
{ Thread.Sleep(100); DoEvents();
}

[...]

/// <summary>MessageLoop abarbeiten</summary>
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(
DispatcherPriority.Background,
new ThreadStart(delegate { }));
}


Hintergründe auch in:

[Bearbeiten von Steuerelementen aus Threads]
http://dzaebel.net/ControlInvoke.htm


ciao Frank
--
Dipl.Inf. Frank Dzaebel [MCP/MVP C#]
http://Dzaebel.NET
Jan Baer
2008-02-19 11:18:39 UTC
Permalink
Hallo Frank,

Danke für Deine Tips. Das Problem ist halt, das ich erst mit dem neuen
Task für den Backgroundworker weitermachen kann, wenn der erste Task
komplett abgebrochen ist. Mein Problem ist, das der Abbruch ja auch
Asynchron läuft. Das müsste ich, wie Du schon sagst, eher über
Eventhandler abhandeln.

Eine andere Lösung wäre aber, das Abbrechen als Synchrone Methode in
einer Ableitung des Backgroundworkers zu kapseln. Ich habe da hier einen
interessanten Artikel gefunden:

"Creating a better BackgroundWorker, CancelImmediately and other
goodies" - http://weblogs.asp.net/rosherove/pages/BackgroundWorkerEx.aspx

Ich habe den mal bei mir eingebaut und das funktioniert jetzt so, wie
ich es für meinen Anwendungsfall brauche.
--
Vielen Dank für Eure Hilfe im voraus!

Beste Grüße

Jan
Frank Dzaebel
2008-02-19 11:33:48 UTC
Permalink
Hallo Jan,
Post by Jan Baer
Ich habe den mal bei mir eingebaut und das funktioniert
jetzt so, wie ich es für meinen Anwendungsfall brauche.
wie gesagt, ein DoEvents() nach Thread.Sleep sollte
auch funktionieren,
wenn Du die anderen Fehler aus Deinem Code wie z.B.
RunWorkerAsynch erst am Ende beseitigt hast.

Ein Ausweichen auf komplexere Implementierungen
ist nicht notwendig.


ciao Frank
--
Dipl.Inf. Frank Dzaebel [MCP/MVP C#]
http://Dzaebel.NET

Joachim Fuchs
2008-02-18 21:56:18 UTC
Permalink
Hallo Jan,
Post by Jan Baer
Im DoWork-Ereignis frage ich CancellationPending ab und setze dann
eventArgs.Cancel = true.
was für eventArgs?

Du musst DoWorks beenden:
if (bgw.CancellationPending) return;

Beachte beim Abbrechen die Racing-Conditions, wie sie in der Doku
beschrieben sind.

Gruß
Joachim
--
Dr. Joachim Fuchs - Autor - Dozent - Softwarearchitekt
MCT - MCAD
http://www.fuechse-online.de/beruflich/index.html -
http://vbnet.codebooks.de
Jan Baer
2008-02-19 05:44:07 UTC
Permalink
Hallo Joachim,
Post by Joachim Fuchs
was für eventArgs?
if (bgw.CancellationPending) return;
das DoWorks Ereignis hat EventArgs mit der Eigenschaft Cancel die man auf
True setzen muss, wenn CancellationPending = true ist. Danach verlasse ich
die Methode mit return.

BG Jan
Loading...