Reputation: 36753
I'm trying to use a Background Worker in a WPF application. The heavy lifting task uses WebClient to download some HTML and parse some info out of it. Ideally I want to do that downloading and parsing without locking the UI and placing the results in the UI once it's done working.
And it works fine, however, if I quickly submit the "download and parse" command, I get the error:
This BackgroundWorker is currently busy and cannot run multiple tasks concurrently
So I did some Googling and it seems that I can enable the .WorkerSupportsCancellation
property of the background worker and just .CancelAsync()
. However, this doesn't work as expected (canceling the current download and parse).
I still get the above error.
Here's my code:
//In window constructor.
_backgroundWorker.WorkerSupportsCancellation = true;
_backgroundWorker.DoWork += new DoWorkEventHandler(_backgroundWorker_DoWork);
_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_backgroundWorker_RunWorkerCompleted);
//Declared at class level variable.
BackgroundWorker _backgroundWorker = new BackgroundWorker();
//This is the method I call from my UI.
private void LoadHtmlAndParse(string foobar)
{
//Cancel whatever it is you're doing!
_backgroundWorker.CancelAsync();
//And start doing this immediately!
_backgroundWorker.RunWorkerAsync(foobar);
}
POCOClassFoo foo = new POCOClassFoo();
void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//This automagically sets the UI to the data.
Foo.DataContext = foo;
}
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//DOING THE HEAVY LIFTING HERE!
foo = parseanddownloadresult()!
}
Upvotes: 0
Views: 16712
Reputation: 17327
Calling CancelAsync
will still fire the RunWorkerCompleted
event. In this event, you need to make sure that CancelAsync
has not been called, by checking e.Cancelled
. Until this event fires, you cannot call RunWorkerAsync
.
Alternatively, I would recommend you do what Tigran suggested and create a new BackgroundWorker
each time.
Further more, I would recommend storing the results of_backgroundWorker_DoWork
in e.Result
, then retrieve them from the same in _backgroundWorker_RunWorkerCompleted
Maybe something like this
BackgroundWorker _backgroundWorker;
private BackgroundWorker CreateBackgroundWorker()
{
var bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += _backgroundWorker_DoWork;
bw.RunWorkerCompleted += new _backgroundWorker_RunWorkerCompleted;
return bw.
}
private void LoadHtmlAndParse(string foobar)
{
//Cancel whatever it is you're doing!
if (_backgroundWorer != null)
{
_backgroundWorker.CancelAsync();
}
_backgroundWorker = CreateBackgroundWorker();
//And start doing this immediately!
_backgroundWorker.RunWorkerAsync(foobar);
}
//you no longer need this because the value is being stored in e.Result
//POCOClassFoo foo = new POCOClassFoo();
private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
//Error handling goes here.
}
else
{
if (e.Cancelled)
{
//handle cancels here.
}
{
//This automagically sets the UI to the data.
Foo.DataContext = (POCOClassFoo)e.Result;
}
}
private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//DOING THE HEAVY LIFTING HERE!
e.Result = parseanddownloadresult()!
}
Upvotes: 6
Reputation: 62276
The thing is that CancelAsync()
does what it climes: cancel in async way. That means that it will not stop immediately, but after some time. That time can never be calculated or predicted, so you have a couple of options:
Wait until this backround worker
stops really, by waiting in cycle until IsBusy property of it becomes false
Or, I think, better solution is to start another background worker
, considering that request of cancelation was already sent to the first one, so it will be soon or later stop. In this case, you need to know from which background worker
data comes, in order to process it or not, cause on start of second the first one will still run and pump the data from WebService
.
Hope this helps.
Upvotes: 5
Reputation: 2387
You are calling CancelAsync without waiting for the background worker to actually cancel the work. Also you must have your own logic for cancelling the work. There is a good example on MSDN which shows how to do it. Basically in your parseanddownloadresult() method you need to check the CancellationPending property.
Upvotes: 0
Reputation: 1015
You need to verify before you kicks in.
f( !bw.IsBusy )
bw.RunWorkerAsync();
else
MessageBox.Show("Can't run the bw twice!");
Upvotes: 0
Reputation: 22516
CancelAsync returns before the worker cancels and stops its work. Hence, your RunWorkerAsync call is starting before the worker is ready, and you're getting that error. You'll need to wait for the worker to be ready first.
Upvotes: 2
Reputation: 25591
When I'm not interested in tracking progress of an async operation, I tend to prefer to just slap a lambda at ThreadPool.QueueUserWorkItem
instead of instantiating and setting up a background worker that I have to check the state of to be able to reuse in a sane way.
Upvotes: 0