Call Method From BackgroundWorker - c#

I hate that my first question seems to have been answered many times, but I'm still having a tough time getting my head around how to call a method using BackgroundWorker.
I'm processing a very large text file using a series of classes and methods. The entire process is kicked off after the user selects a tool strip item. Sequentially, it goes like this:
User selects the tool strip item
User selects a file to be processed via a dialog box
The action starts
I think I can wrap everything into BackgroundWorker from the moment the user pops the initial dialog box, but what I'd like to do for now is just put the method where all the heavy lifting is done into its own instance of BackGroundWorker. I'll add a ProgressBar, too, but I think I can handle that if I can just get the BackgroundWorker process rolling.
From the top (pseudocode used for example purposes. Much omitted for brevity):
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
string fileName = openSingleFile.FileName;
processFile(fileName);
}
static public void processFile(string fileName)
{
// many vars/loops exist but not shown
foreach (data in bigData)
{
processItem(stringA, stringB); // <-- this method is where the expensive work is done
x++;
}
}
I've created an instance of BackgroundWorker...:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Things go here
}
...and I've tried too many things to list, so I've gone back to the beginning for the presentation above.
If I'm understanding BackgroundWorker, I'll need to do the following:
Replace processItem(stringA, stringB) in the above code with something like:
backgroundWorker1.RunWorkerAsync(processItem(stringA, stringB));
...and then do some type of DoWork call?
...and then do some type of RunWorkerCompleted call?
Not sure why my brain is freezing, but I'm embarrassed at the amount of time I've spent on this with no result. Any help would be greatly appreciated. Without StackOverflow, I would have been DOA a long time ago.
FYI: I've referenced other SO posts, MSDN, and DotNetPerls examples. I'm just missing something conceptually, I suppose.

Replace processItem(stringA, stringB) in the above code with something like...
No, that's how you got in trouble. You most definitely want to move the processFile() call to the worker. There is no perceivable benefit from running processItem() in a worker, at least not in the snippet you posted. And doing so is difficult, it would require starting more than one worker. One for each item. Having a lot of workers that each do little work is not very healthy. If it is really necessary then you don't want to use BackgroundWorker, you'll want an entirely different approach with several Threads that consume packets of work from a thread-safe queue. Don't go there if you can avoid it.
The only non-trivial problem to solve is passing the string that processFile() needs. Luckily BackgroundWorker.RunWorkerAsync() has an overload that takes a single object. Pass your string. Obtain its value in your DoWork event handler, casting e.Argument back to a string. Thus:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
string path = (string)e.Argument;
processFile(path);
}
private void processToolStripMenuItem_Click(object sender, EventArgs e) {
backgroundWorker1.RunWorkerAsync(openSingleFile.FileName);
processToolStripMenuItem.Enabled = false;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
processToolStripMenuItem.Enabled = true;
}

Starting up a new background worker is an expensive operation. You don't want to be starting one for each iteration of a loop. Instead, put the entire loop inside of a single background worker's scope.
When ToolStripMenuItem_Click is run create your background worker, have processFile be what is done in the DoWork event handler.
Make sure that when doing that work you're really just doing that work, not updating the UI. You'll want to separate business logic from the user interface. If you want to update the UI with some current progress then call ReportProgress and ensure that there is an event handler to properly update the UI.
If you need to update the UI when the work is all done then you can do so in the RunWorkerCompleted event handler. If the work you are doing generates some result that is used to update the UI use the Result property of the background worker to pass it from the DoWork method to the completed handler.

BackgroundWorker bgw;
In the Load event or constructor:
bgw = new BackgroundWorker();
bgw.WorkerReportsProgress = true;
//bgw.WorkerSupportsCancellation = true;
bgw.DoWork += bgw_DoWork;
bgw.ProgressChanged += bgw_ProgressChanged;
bgw.RunWorkerCompleted += bgw_RunWorkerCompleted;
/
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
string fileName = openSingleFile.FileName;
bgw.RunWorkerAsync(fileName);
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
string fileName = (string)e.Argument;
processFile(fileName);
}
private void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
int Progress = e.ProgressPercentage;
//Update progressbar here
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Job completed
}

Related

correct way to re-call backgroundworker

As in title, I have some bgw I want to call on every button press.
Is this code correct ?
private static BackgroundWorker bgw = null;
private void bttn_Click(...)
{
if(!bgw.IsBusy)
doSomeWorkInBg();
else
MessageBox.Show("Slow down a bit");
}
private void doSomeWorkInBg()
{
if (bgw == null)
{
bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
}
bgw.RunWorkerAsync();
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
// do some work that takes time
}
Or maybe I should add RunWorkerCompleted event with "bgw = null;" code or something like that?
You should avoid making your bgw static, because you are using it in non-static context.
When I need to re-run the worker frequently based on the UI event, this is the construct that I usually use:
bool ShouldRunWorkedASAP;
private void bttn_Click(...){
ShouldRunWorkedASAP=true;
if (!bgw.IsBusy) bgw.RunWorkerAsync();
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
while (ShouldRunWorkedASAP) {
ShouldRunWorkedASAP=false;
// do some work that takes time
}
}
This assumes that the worked is instantiated in the class constructor. Basically this code sets the flag that the worker should run ASAP whenever the UI event occurred, then tries to run the worker. If it is already running - then the while() loop inside the worker implementation will schedule the job for the next run as soon as it completes.
This code does not ensure that the worker will run exactly the number of times the user presses the button, not sure if in your case this is required or not.

Backgroundworker, not running the progressbar

How can I fix this issue ?
I am expecting the progressbar to load during process untill process it is done
Here is my code:
private void btnProcess_Click(object sender, EventArgs e)
{
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//start transaction
DoTransaction();
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
My transaction function:
private void DoTransaction()
{
string pathIdentifier;
pathIdentifier = func.checkthePathFile();
if (pathIdentifier == null)
{
MessageBox.Show("Path has no yet been specified!");
}
else
{
//create xml base from user inputs
XElement transactXML = new XElement("Transaction",
new XElement("CardNumber", txtCardNum.Text.Trim()),
new XElement("ExpireDate", txtExpDate.Text.Trim()),
new XElement("Cardtype", txtCardType.Text.Trim())
);
//save xml to a file
transactXML.Save(pathIdentifier + "/sample.xml");
}
}
How is the runtime supposed to know how far along your process is?
You need to tell it by calling backgroundWorker.ReportProgress from the background operation. No magic here.
MSDN: http://msdn.microsoft.com/en-us/library/ka89zff4.aspx
Break down your process into meaningful chunks and ReportProgress whenever it makes sense to do so.
public void DoTransaction()
{
part1();
backgroundWorker.ReportProgress(25);
part2();
backgroundWorker.ReportProgress(50);
part3();
backgroundWorker.ReportProgress(75);
part4();
backgroundWorker.ReportProgress(100);
}
Edit Based on Posting of Transaction() function
If you are not confident in writing multithreaded programs, then do not attempt to write multithreaded programs, even with the help of a BackgroundWorker which tries to abstract some of those details away from you.
A few issues:
Your provided Transaction() method attempts to launch a MessageBox and read the Text property of various controls from the background thread. This is going to cause problems as the runtime typically throws an Exception when UI elements are accessed from a thread other than the one which created them.
If you really want to do the XML saving in the BackgroundWorker, you should validate the filename and directory, and save the Text properties to an intermediate object before setting up the BackgroundWorker and calling RunWorkerAsync.
Furthermore, in my opinion, your Transaction method is not going to be time intensive enough to truly warrant a background thread. Even a relatively old PC will be able to create and save a 15 element XML file faster than you can blink. The runtime will probably waste more time marshalling data between the threads than it would to simply write the file out to disk. Just do your work in the button click event handler.
needs some reference to the BackgroundWorker instance.pass the reference to the class when instantiating it.
instantiate like this
BackgroundWorker worker = sender as BackgroundWorker;
then call like this
`worker.ReportProgress(...)`

Start second method after first method is finished with thread functionality c#

I have a method which is rebuilding the product catalog of a webshop. This is neccessary after I change some product information. After the rebuilding method I would like to start a second method to generate full text index of the webshop. I can watch the status of the first method (RebuildCatalog). If the status is "RebuildFinished" then I would like to start the second method (GenerateFullTextIndex). I would like to use Threads functionality. Does someone can create an example of how to implementate this scenario?
I would like to use Threads functionality.
It really doesn't sound like you do. Starting one method after another finishes is as simple as:
var status = RebuildCatalog();
if (status == Status.RebuildFinished)
{
GenerateFullTextIndex();
}
No threading required. If you really think you need multiple threads, you should explain why you think they'll help. At what point do you need to perform multiple tasks concurrently?
Well, if you want to use a multiple threads and oranize your calls in chain so they are executed on another thread, but in sequence, and you are using .NET Framework 4.0>, you can use a Task Parallelism, like for example using Task::ContinueWith method.
Example (preudocode from MSDN):
Task<byte[]> getData = new Task<byte[]>(() => GetFileData());
Task<double[]> analyzeData = getData.ContinueWith(x => Analyze(x.Result));
Task<string> reportData = analyzeData.ContinueWith(y => Summarize(y.Result));
getData.Start();
//or...
Task<string> reportData2 = Task.Factory.StartNew(() => GetFileData())
.ContinueWith((x) => Analyze(x.Result))
.ContinueWith((y) => Summarize(y.Result));
Using events would seem to be simpler than watching the status.
In your rebuild catalog code fire a "finished" event on completion:
public event EventHandler<EventArgs> RebuildFinished;
private void Rebuild(...)
{
// Rebuild the catalog
this.RebuildFinished(this, new EventArgs(...));
}
Then handle it:
this.catalog.RebuildFinished += this.RebuildFinished;
private void RebuildFinished(object sender, EventArgs e)
{
// Rebuild the index
}
Now both of these can (and probably should) be using threads to ensure that the UI of your application stays responsive:
this.catalogThread = new Thread(new ThreadStart(this.catalog.Rebuild));
As I can assume from your question your rebuilding method probably takes up considerable time and that is why you want to run in a separate thread. Therefore I would suggest implementing Event based async pattern. When your rebuilding (async) method finishes it will throw finished event with AsyncCompletedEventArgs (which you can subclass to pass your result status) and from there you will start your second method.
BackgroundWorker bw1 = new BackgroundWorker();//To rebuild catalog.
BackgroundWorker bw2 = new BackgroundWorker();//To generate text.
public Form1()
{
InitializeComponent();
bw1.DoWork += bw1_DoWork;
bw1.RunWorkerCompleted += bw1_RunWorkerCompleted;
bw2.DoWork += bw2_DoWork;
bw2.RunWorkerCompleted += bw2_RunWorkerCompleted;
bw1.RunWorkerAsync();//Start new thread. - Rebuild catalog.
}
void bw1_DoWork(object sender, DoWorkEventArgs e)
{
//Rebuild catalog.
}
void bw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
bw2.RunWorkerAsync();//Generate text.
}
void bw2_DoWork(object sender, DoWorkEventArgs e)
{
//Generate text.
}
void bw2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Whatever...
}

Is this safe to unsubscribe DoWork after calling RunWorkerAsync but before the function exits?

I have many methods (they only run one at a time though), they all use the same RunWorkerCompleated and ProgressChanged methods but they all have different Dowork methods. Is it safe to do the following:
private void button_Process_Click(object sender, EventArgs e)
{
bgWork_Process.DoWork += Scrub_DoWork;
bgWork_Process.RunWorkerAsync();
bgWork_Process.DoWork -= Scrub_DoWork;
}
or can I hit a edge case doing this? I did not see anything on the MSDN on it saying it was not allowed and it as (so far) run fine in my program, but I wanted to check here to see if anyone has run in to trouble doing this.
What you could do to make sure that the Event Handler isn't being removed until you are done with it would be to do something similar to
Action DoWorkAction;
private void button_Process_Click(object sender, EventArgs e)
{
gbHistory.Enabled = false;
gbScrub.Enabled = false;
DoWorkAction = new Action(Scrub_DoWork);
bgWork_Process.DoWork += DoWorkAction;
bgWork_Process.RunWorkerAsync();
}
And in whatever handles your completion
private void bgWork_Process_CompletedHandler(object sender, EventArgs e)
{
bgWork_Process.DoWork -= DoWorkAction;
}
I do feel, however; that it may be better to just have separate BackGroundWorkers for all of your Actions that you need to perform instead of sharing a similar one with that or wrap in a class so you can be more clear about what you are doing.

How to stop BackgroundWorker on Form's Closing event?

I have a form that spawns a BackgroundWorker, that should update form's own textbox (on main thread), hence Invoke((Action) (...)); call.
If in HandleClosingEvent I just do bgWorker.CancelAsync() then I get ObjectDisposedException on Invoke(...) call, understandably. But if I sit in HandleClosingEvent and wait for bgWorker to be done, than .Invoke(...) never returns, also understandably.
Any ideas how do I close this app without getting the exception, or the deadlock?
Following are 3 relevant methods of the simple Form1 class:
public Form1() {
InitializeComponent();
Closing += HandleClosingEvent;
this.bgWorker.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
while (!this.bgWorker.CancellationPending) {
Invoke((Action) (() => { this.textBox1.Text = Environment.TickCount.ToString(); }));
}
}
private void HandleClosingEvent(object sender, CancelEventArgs e) {
this.bgWorker.CancelAsync();
/////// while (this.bgWorker.CancellationPending) {} // deadlock
}
The only deadlock-safe and exception-safe way to do this that I know is to actually cancel the FormClosing event. Set e.Cancel = true if the BGW is still running and set a flag to indicate that the user requested a close. Then check that flag in the BGW's RunWorkerCompleted event handler and call Close() if it is set.
private bool closePending;
protected override void OnFormClosing(FormClosingEventArgs e) {
if (backgroundWorker1.IsBusy) {
closePending = true;
backgroundWorker1.CancelAsync();
e.Cancel = true;
this.Enabled = false; // or this.Hide()
return;
}
base.OnFormClosing(e);
}
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if (closePending) this.Close();
closePending = false;
// etc...
}
I've found another way. If you have more backgroundWorkers you can make:
List<Thread> bgWorkersThreads = new List<Thread>();
and in every backgroundWorker's DoWork method make:
bgWorkesThreads.Add(Thread.CurrentThread);
Arter that you can use:
foreach (Thread thread in this.bgWorkersThreads)
{
thread.Abort();
}
I used this in Word Add-in in Control, which i use in CustomTaskPane. If someone close the document or application earlier then all my backgroundWorkes finishes their work, it raises some COM Exception(I don't remember exatly which).CancelAsync() doesn't work.
But with this, I can close all threads which are used by backgroundworkers Immediately in DocumentBeforeClose event and my problem is solved.
Here was my solution (Sorry it's in VB.Net).
When I run the FormClosing event I run BackgroundWorker1.CancelAsync() to set the CancellationPending value to True. Unfortunately, the program never really gets a chance to check the value CancellationPending value to set e.Cancel to true (which as far as I can tell, can only be done in BackgroundWorker1_DoWork).
I didn't remove that line, although it doesn't really seem to make a difference.
I added a line that would set my global variable, bClosingForm, to True. Then I added a line of code in my BackgroundWorker_WorkCompleted to check both e.Cancelled as well as the global variable, bClosingForm, before performing any ending steps.
Using this template, you should be able to close your form out at any time even if the backgroundworker is in the middle of something (which might not be good, but it's bound to happen so it might as well be dealt with). I'm not sure if it's necessary, but you could dispose the Background worker entirely in the Form_Closed event after this all takes place.
Private bClosingForm As Boolean = False
Private Sub SomeFormName_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
bClosingForm = True
BackgroundWorker1.CancelAsync()
End Sub
Private Sub backgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'Run background tasks:
If BackgroundWorker1.CancellationPending Then
e.Cancel = True
Else
'Background work here
End If
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
If Not bClosingForm Then
If Not e.Cancelled Then
'Completion Work here
End If
End If
End Sub
Can you not wait on the signal in the destructor of the form?
AutoResetEvent workerDone = new AutoResetEvent();
private void HandleClosingEvent(object sender, CancelEventArgs e)
{
this.bgWorker.CancelAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (!this.bgWorker.CancellationPending) {
Invoke((Action) (() => { this.textBox1.Text =
Environment.TickCount.ToString(); }));
}
}
private ~Form1()
{
workerDone.WaitOne();
}
void backgroundWorker1_RunWorkerCompleted( Object sender, RunWorkerCompletedEventArgs e )
{
workerDone.Set();
}
Firstly, the ObjectDisposedException is only one possible pitfall here. Running the OP's code has produced the following InvalidOperationException on a substantial number of occasions:
Invoke or BeginInvoke cannot be called
on a control until the window handle
has been created.
I suppose this could be amended by starting the worker on the 'Loaded' callback rather than the constructor, but this entire ordeal can be avoided altogether if BackgroundWorker's Progress reporting mechanism is used. The following works well:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (!this.bgWorker.CancellationPending)
{
this.bgWorker.ReportProgress(Environment.TickCount);
Thread.Sleep(1);
}
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.textBox1.Text = e.ProgressPercentage.ToString();
}
I kind of hijacked the percentage parameter but one can use the other overload to pass any parameter.
It is interesting to note that removing the above sleep call clogs the UI, consumes high CPU and continually increases the memory use. I guess it has something to do with the message queue of the GUI being overloaded. However, with the sleep call intact, the CPU usage is virtually 0 and the memory usage seems fine, too. To be prudent, perhaps a higher value than 1 ms should be used? An expert opinion here would be appreciated... Update: It appears that as long as the update isn't too frequent, it should be OK: Link
In any case, I can't foresee a scenario where the updating of the GUI has to be in intervals shorter than a couple of milliseconds (at least, in scenarios where a human is watching the GUI), so I think most of the time progress reporting would be the right choice
I really dont see why DoEvents is regarded as such a bad choice in this case if you are using this.enabled = false. I think it would make it quite neat.
protected override void OnFormClosing(FormClosingEventArgs e) {
this.Enabled = false; // or this.Hide()
e.Cancel = true;
backgroundWorker1.CancelAsync();
while (backgroundWorker1.IsBusy) {
Application.DoEvents();
}
e.cancel = false;
base.OnFormClosing(e);
}
Your backgroundworker should not use Invoke to update the textbox. It should ask the UI thread nicely to update the textbox using event ProgressChanged with the value to put in the textbox attached.
During event Closed (or maybe event Closing), the UI thread remembers that the form is closed before it cancels the backgroundworker.
Upon receiving the progressChanged the UI thread checks if the form is closed and only if not, it updates the textbox.
This won't work for everyone, but if you are doing something in a BackgroundWorker periodically, like every second or every 10 seconds, (perhaps polling a server) this seems to work well to stop the process in an orderly manner and without error messages (at least so far) and is easy to follow;
public void StopPoll()
{
MyBackgroundWorker.CancelAsync(); //Cancel background worker
AutoResetEvent1.Set(); //Release delay so cancellation occurs soon
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
while (!MyBackgroundWorker.CancellationPending)
{
//Do some background stuff
MyBackgroundWorker.ReportProgress(0, (object)SomeData);
AutoResetEvent1.WaitOne(10000);
}
}
I'd pass in the SynchronizationContext associated with the textbox to the BackgroundWorker and use that to perform Updates on the UI thread. Using SynchronizationContext.Post, you can check if the control is disposed or disposing.
What about Me.IsHandleCreated?
Private Sub BwDownload_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BwDownload.RunWorkerCompleted
If Me.IsHandleCreated Then
'Form is still open, so proceed
End If
End Sub
Another way:
if (backgroundWorker.IsBusy)
{
backgroundWorker.CancelAsync();
while (backgroundWorker.IsBusy)
{
Application.DoEvents();
}
}
One solution that works, but too complicated. The idea is to spawn the timer that will keep trying to close the form, and form will refuse to close until said bgWorker is dead.
private void HandleClosingEvent(object sender, CancelEventArgs e) {
if (!this.bgWorker.IsBusy) {
// bgWorker is dead, let Closing event proceed.
e.Cancel = false;
return;
}
if (!this.bgWorker.CancellationPending) {
// it is first call to Closing, cancel the bgWorker.
this.bgWorker.CancelAsync();
this.timer1.Enabled = true;
}
// either this is first attempt to close the form, or bgWorker isn't dead.
e.Cancel = true;
}
private void timer1_Tick(object sender, EventArgs e) {
Trace.WriteLine("Trying to close...");
Close();
}

Categories