Background Worker Updating from a different class (preferably via events) - c#

I have a background worker in my GUI class.
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
ProgressClass obj = new ProgressClass();
Importer tradeImporter = e.Argument as Importer;
BackgroundWorker worker = sender as BackgroundWorker;
List<TradeUploadInfo> list = obj.AllocateTrades2(tradeImporter, false);
e.Result = list; //Passes the list for processing
}
Importer is my own class. Now, the AllocateTrades2 method has all the processing done in it.
My question is, how would I go about performing a bw.ProgressReport inside the AllocateTrades2 method, which is in a different class without passing the bw as a parameter?
Would be great if someone explained it to me how to do it with events, but if there is another elegant way. I'm open for ideas.

If you don't want to pass in the entire BGW (justifiably so) so as to not expose more than it needs to know, one option is to just pass in a delegate that you assign a ReportProgress call to.
Adjust the signature of AllocateTrades2 to be:
public List<TradeUploadInfo> AllocateTrades2(
Importer importer, bool flag, Action<int> reportProgress)
Invoke the reportProgress delegate as appropriate from within that method.
Then adjust the call to AllocateTrades2 like so:
obj.AllocateTrades2(tradeImporter, false,
progress => worker.ReportProgress(progress));

Well, given the fact that AllocateTrades2 runs in the context of the background worker, any events that it raises are also executed in that context.
So, all you need to do is add a new event in your ProgressClass, say NotifyProgress, and bind it to the class where you have the background worker.
So:
//In class ProgressClass.
public event EventHandler<ProgressClassEventArgs> NotifyProgress = (s, e) => {};
And next:
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
ProgressClass obj = new ProgressClass();
//Here you hook up the event
obj.NotifyProgress += this.OnProgressChanged;
Importer tradeImporter = e.Argument as Importer;
BackgroundWorker worker = sender as BackgroundWorker;
List<TradeUploadInfo> list = obj.AllocateTrades2(tradeImporter, false);
e.Result = list; //Passes the list for processing
}
The event handler would look like this:
private void OnProgressChanged(object sender, ProgressClassEventArgs e)
{
worker.ReportProgress(e.Progress);
}
It's OK, since you can (or you already do) have the worker as a member in this class.
You will need to define the ProgressClassEventArgs (EventArgs subclass) and add a Progress property of type int in this case, to match the ReportProgress args.

If you are able/willing to modify the obj.AllocateTrades2 method, you can yield results and then add each item to your list in a loop.
Example:
public IEnumerable<TradeUploadInfo> AllocateTrades2(Importer tradeImporter, bool foo)
{
foreach( ... )
{
TradeUploadInfo bar; // = ...
// ...
yield return bar;
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
ProgressClass obj = new ProgressClass();
Importer tradeImporter = e.Argument as Importer;
BackgroundWorker worker = sender as BackgroundWorker;
List<TradeUploadInfo> list = new List<TradeUploadInfo>();
foreach ( TradeUploadInfo info in obj.AllocateTrades2(tradeImporter, false) )
{
list.Add( info );
// ... progress
}
e.Result = list; //Passes the list for processing
}
The beauty here is that you can use AllocateTrades2 exactly as you did before (meaning you don't have to modify existing code or overload the function) (hmm.. actually, you would need to modify code that was explicitly expecting a List, probably by just adding .ToList() after the function call) and you don't need to add events (which can get a little tricky when it comes to garbage collection).

Related

Update process from within method called by backgroundworker c#

I have an application with a gui and a Rich Text Box where I output what the program is currently doing since data processing can be quite long.
I tried two approaches for that:
1 In the Backgroundworker method I can just call the following code fine:
GlobalVar.backgroundWorkerAppendText = task.Build_CSV_List();
Processchange();
Whereas I cannot use Form1.Processchange(); in the helper class due to the non static context
2 Therefore I tried to create my very first eventhandler.
The Idea was that helper.UpdateConsole() would raise an event
public event EventHandler OnConsoleUpdate;
public void Consoleupdate()
{
OnConsoleUpdate(this, EventArgs.Empty);
}
to which the Backgroundworker listens and then calls Processchange from its context
public void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
StandardTasks task = new StandardTasks();
Helper helper = new Helper();
helper.OnConsoleUpdate += Processchange;
task.DoSomeStuffHere()
}
public void Processchange(object sender=null, EventArgs e=null)
{
//MessageBox.Show(GlobalVar.backgroundWorkerAppendText);
GlobalVar.next = false;
backgroundWorker1.ReportProgress(1);
while (GlobalVar.next == false)
{
helper.TimeBreaker(100,"ms");
}
}
Unfortunately this was was not successful. As soon as rising the Event I get the errormessage System.NullReferenceException which -after googling- leads me to the conclusion that there is no listerner attached to the event eventhouh I attached it in the Backgroundworker Do work.
Edit: the OnConsoleUpdate() == null as shown on the screenshot below
event = null
The helper is in another class file "helpers" which might be important for a solution.
i hope you guys can help me out.
Welcome to SO!
A few things immediately jump to mind.
First, let's get the event issue out of the way. You've got the correct approach - you need an event and method to call it, but that method should check if the event is null.
Basically, do this:
public event EventHandler OnConsoleUpdate;
public void ConsoleUpdate()
{
OnConsoleUpdate?.Invoke(this, EventArgs.Empty);
}
The above makes use of ?, a null-condition operator. You can read more about it on this MSDN page.
Second thing... it's unclear what your background worker actually IS. It sounds like it's some kind of custom class you crated? The reason it's important is because .NET actually has a BackgroundWorker class used for running operations... well, in the background. It also has an OnProgressChanged event which you can hook up to which could be used to update the UI (just remember to set the WorkerReportsProgress property to true). And to use the BackgroundWorker mentioned above, you shouldn't need to create any events of your own.
Here's how you can use the standard .NET BackgroundWorker:
System.ComponentModel.BackgroundWorker worker = new System.ComponentModel.BackgroundWorker();
void StartBackgroundTask()
{
worker.DoWork += worker_DoWork;
//if it's possible to display progress, use this
worker.WorkerReportsProgress = true;
worker.ProgressChanged += worker_ProgressChanged;
//what to do when the method finishes?
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
//start!
worker.RunWorkerAsync();
}
void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
//perform any "finalization" operations, like re-enable disabled buttons
//display the result using the data in e.Result
//this code will be running in the UI thread
}
//example of a container class to pass more data in the ReportProgress event
public class ProgressData
{
public string OperationDescription { get; set; }
public int CurrentResult { get; set; }
//feel free to add more stuff here
}
void worker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
//display the progress using e.ProgressPercentage or e.UserState
//this code will be running in the UI thread
//UserState can be ANYTHING:
//var data = (ProgressData)e.UserState;
}
void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
//this code will NOT be running in the UI thread!
//you should NOT call the UI thread from this method
int result = 1;
//perform calculations
for (var i = 1; i <= 10; i++)
{
worker.ReportProgress(i, new ProgressData(){ OperationDescription = "CustomState passed as second, optional parameter", CurrentResult = result });
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
result *= i;
}
e.Result = result;
}
Now, the thing about the BackgroundWorker class is that it is rather old, and with current .NET versions you can use the async / await keywords to easily handle background operations and UI updates, but this probably is going outside the bounds of this question. That said, the existence of async / await doesn't invalidate the use of BackgroundWorker which is pretty simple in its usage.
There's one more worrisome thing in your code.
public void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
StandardTasks task = new StandardTasks(); //<- you create a task
Helper helper = new Helper(); // <- you create a helper
helper.OnConsoleUpdate += Processchange; // <- you hook up to the helper event
task.DoSomeStuffHere(); // <- you do stuff with the task... but the task doesn't know about your helper above! Does `StandardTasks` use `Helper`? If so, how?
}
Do note that events, unless made static, aren't global. So hooking up to an event in one instance of a class won't cause another instance of that class to "fire" that event. It seems one way to fix your issues would be to make the StandardTasks class take Helper as one of the constructor parameters, so the code would look like this:
Helper helper = new Helper(); // <- you create a helper
helper.OnConsoleUpdate += Processchange; // <- you hook up to the helper class event to actually do something
StandardTasks task = new StandardTasks(helper); //<- you create a task which will use the helper with the hooked up event above

Updating the GUI from background worker

The name of the question is: "Updating the GUI from background worker", but the correct name world be: "Updating the GUI from background worker OR reporting multiple-variables (other than an integer) from background worker"
Please let me explain my situation. In a program I have a background worker which analyses the information.As the result of this analysis - form GUI elements should be populated with necessary data. In GUI I would like to update
2 datagridviews
1 listbox
5 labels
As I understand - I can only natively report 1 int value via ReportProgress() method of background worker.
So the question is - how can I pass a List<> ( + some other variables: string, int) via ReportProgress()? Basically - i want to update the GUI with the information but "1 integer" just won't do.. So either it should be possible to pass multiple variables via an ReportProgress() OR i can use an Invoke from inside the BackgroundWorker itself to update the GUI.. Personally I don't like the Invoke approach... What's your opinion?
Here is my code (see the comments):
private void button9_Click(object sender, EventArgs e) // start BW
{
bw.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
bw.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.RunWorkerAsync(10);
}
private void button10_Click(object sender, EventArgs e) // cancel BW
{
bw.CancelAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int count = (int)e.Argument;
for (int i = 1; i <= count; i++)
{
if (bw.CancellationPending)
{
e.Cancel = true;
break;
}
List<List<string>> list_result = new List<List<string>>();
list_result = Proccess();
bw.ReportProgress(list_result.Count()); // right now I can only return a single INT
/////////// UPDATE GUI //////////////
// change datagridview 1 based on "list_result" values
// change datagridview 2
// change listbox
// change label 1
// change label ..
Thread.Sleep(20000);
}
MessageBox.Show("Complete!");
e.Result = sum;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
prog_count++;
listBox1.Items.Add("Count: (" + prog_count.ToString() + "/20). Found: " + e.ProgressPercentage.ToString() + ".");
}
There's a UserState parameter when calling ReportProgress.
var list_result = new List<List<string>>();
new backgroundWorker1.ReportProgress(0, list_result);
The parameter type is an object so you'll have to cast it back to the type you need:
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var userState = (List<List<string>>)e.UserState;
}
The tricky issue with this is, how do you determine whether you're passing back a List, or a list of lists, or a single string, number, etc. You'll have to test for each possibility in the ProgressChanged event.
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var myList = e.UserState as List<List<string>>;
if (myList != null)
{
// use list
return;
}
int myNumber;
if (Int32.TryParse(e.UserState.ToString(), out myNumber))
{
// use number
return;
}
var myString = e.UserState.ToString();
// use string
}
Alternatively, you could create a class that holds all the values you need (or use Tuple), run everything in the background to populate that class, then pass that to the RunWorkerCompleted event, and update your UI all at once from there.
I have written two very easy methods that enable you to invoke your code (only if required) and you only need to write your code once. I think this makes Invoke much friendlier to use:
1) BeginInvoke
public static void SafeBeginInvoke(System.Windows.Forms.Control control, System.Action action)
{
if (control.InvokeRequired)
control.BeginInvoke(new System.Windows.Forms.MethodInvoker(() => { action(); }));
else
action();
}
2) Invoke
public static void SafeInvoke(System.Windows.Forms.Control control, System.Action action)
{
if (control.InvokeRequired)
control.Invoke(new System.Windows.Forms.MethodInvoker(() => { action(); }));
else
action();
}
It can be called like this:
SafeInvoke(textbox, () => { textbox.Text = "text got changed"; });
Alternatively you could just
System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false;
(which only changes behaviour in debug mode btw) and look if you run into problems.More often than not you actually don't. It took me quite some time to find cases very Invoke is really required for things not to get messed up.
The basic pattern for updating the UI from another thread is:
If controlItem.InvokeRequired Then
controlItem.Invoke(Sub() controlItem.Text = textUpdateValue)
Else
controlItem.Text = textUpdateValue
End If
This could update your list of controls without requiring you to pass anything through ReportProgress. If you would like to update your control from within the thread, I don't believe you need to check InvokeRequired, because it will always be required. However, best practices might be to expose the setting of a control via a property and then to do the full check so you can call it from anywhere.

Using parameters in BackgroundWorker handlers

For passing data to a BackgroundWorker's DoWork I use a separate wrapper class' instance:
MyParams mpar = new MyParams();
...
mpar.Par1 = par1val;
mpar.Par2 = par2val;
mpar.Par3 = par3val;
...
var worker1 = new System.ComponentModel.BackgroundWorker();
worker1.DoWork += new DoWorkEventHandler(worker1_DoWork);
worker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker1_RunWorkerCompleted);
worker1.RunWorkerAsync(mpar);
Then I can use parameters of mpar instance in worker1_DoWork, operating in another thread.
void worker1_DoWork(object sender, DoWorkEventArgs e)
{
//here we use mpar.Par1, mpar.Par2 and so on
}
In RunWorkerCompletedEventHandler we do some postactions in UI thread.
My question is : Can we use in RunWorkerCompleted handler the mpar instance, which worked just before in DoWork handler and can we be sure its values are the same it had in DoWork? If not, what is the correct approach for sharing parameters for various stages of BackgroundWorker operation?
void worker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Dispatcher.BeginInvoke((Action)(() =>
{
//Can we use the mpar instance here?
}
));
}
You can assign the value of e.Result in worker1_DoWork
static void worker1_DoWork(object sender, DoWorkEventArgs e)
{
//Do the work
//...
e.Result = new MyParams();
}
Then you can get it in the worker1_RunWorkerCompleted in e.Result.
If you need to pass additional result value and you don't want to put MyParams object in the worker1_DoWork: e.Result - then you can create a small class ResultHolder with MyParams and MyResult as properties and use that class to pass the result in worker1_DoWork

BackgroundWorker - C#

I am developing a multithreading application using BackroundWorker. In the Do_Work method I call another method, in that method I add a lot of data into a list using a while-statement. My goal is to add all the data that in the list to show in a GridView. How can I do that so every time data adds to the list, the gridview uppdates? Instead of waiting until that the while-statement has finished running. When the while-statment adds a value to the list, the value inserts into the gridview?
It must be in the ProgressChanged, but I dont know how to do that.
Add a progress changed event handler to your worker
In your Do_Work method
BackgroundWorker worker = sender as BackgroundWorker;
worker.ReportProgress(0, new DataObject())
In your progess handler
DataObject data (DataObject)e.UserState;
yourList.Add(data);
If you know how far you are along you can send the actual done count in ReportProgess instead of 0.
My method was to create a class that would hold any data that I am passing back and forth. When you call ReportProgress it will require the percentage increment and an object argument. You put your class into this object argument, and from the ProgressChanged event this object is made available through the ProgressChangedEventArgs. You can then read this data, input it into the control you are wanting to update and then call the Refresh() method on that control to update it in the UI without freezing the interface.
http://msdn.microsoft.com/en-us/library/a3zbdb1t.aspx
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.progresschanged.aspx
Edit: (psuedocode, untested)
private List<Customer> _customerList = new List<Customer>();
protected void DoWork(object sender, DoWorkEventArgs e)
{
MyDataGridView.DataSource = _customerList; // Here is where you'll set your data source and bindings
Load_Customer_Method();
}
private void Load_Customer_Method()
{
int totalCustomers = 20;
int currentCustomer = 1;
for(currentCustomer = 1; currentCustomer <= totalCustomers; currentCustomer++)
{
Customer c = new Customer();
// Configure customer
ReportProgress(100*currentCustomer/totalCustomers, c);
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Customer addedCustomer = (Customer)e.UserState;
_customerList.Add(addedCustomer); // If bindings are set, this update is automatic
MyDataGridView.Refresh(); // Force a refresh of the screen for this grid to make
// it appear as if the grid is populating one object at
// a time.
}
This might help. I have a class called WorkerThread that does the work we want
static void Main( string[] args )
{
// create the background worker object
BackgroundWorker _worker = new BackgroundWorker();
// tell the background worker it can be cancelled and report progress
_worker.WorkerReportsProgress = true;
_worker.WorkerSupportsCancellation = true;
// a worker thread object where the actual work happens
WorkerThread thread = new WorkerThread();
// add our event handlers
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler( thread.RunWorkerCompleted );
_worker.ProgressChanged += new ProgressChangedEventHandler( thread.ProgressChanged );
_worker.DoWork += new DoWorkEventHandler( thread.DoWork );
// start the worker thread
_worker.RunWorkerAsync();
// wait for it to be completed
while( !_worker.CancellationPending )
{
// sleep for a second
Thread.Sleep( 1000 );
}
Console.ReadKey();
}
Now in the WorkerThread class
public class WorkerThread
{
public void DoWork( object sender, DoWorkEventArgs e )
{
//get a handle on the worker that started this request
BackgroundWorker workerSender = sender as BackgroundWorker;
// loop through 10 times and report the progress back to the sending object
for( int i = 0; i < 10; i++ )
{
// tell the worker that we want to report progress being made
workerSender.ReportProgress( i );
Thread.Sleep( 100 );
}
// cancel the thread and send back that we cancelled
workerSender.CancelAsync();
e.Cancel = true;
}
public void RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
{
Console.WriteLine( "Worker Done!!" );
}
public void ProgressChanged( object sender, ProgressChangedEventArgs e )
{
// print out the percent changed
Console.WriteLine( e.ProgressPercentage );
}
}
I am using a custom class, you could use a method in your class that is creating the background worker. Just modify the code in the ProgressChanged event.
I suggest you to read on the backgroundworker. You have all the info and example there :
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
All you will need to do is to pass your item as parameter in ReportProgress and in the event ProgressChanged add your item in your grid.

How do you set the UserState in the RunWorkerCompletedEventArgs object?

HI all.
I have an array of BackgroundWorker objects running instances of a Worker class. When I call the Worker class the object instance does it's thing and then runs out of code (the loop finishes). I'm able to listen to the RunWorkerCompleted() event but when it calls the delegate that I've set up I need to know which of my Worker objects just completed.
I see a UserState property in the RunWorkerCompletedEventArgs that comes to my delegate but I have no idea how to set this in my Worker object as it's finishing.
Any ideas?
snippet from my WorkManager.cs class
public Worker AddWorker()
{
Worker w = new Worker();
_workers.Add(w.WorkerID,w);
BackgroundWorker bg = new BackgroundWorker();
_bgworkers.Add(bg);
bg.DoWork += w.Start;
bg.WorkerReportsProgress = true;
bg.WorkerSupportsCancellation = true;
bg.ProgressChanged += ProcessWorkerMessage;
bg.RunWorkerCompleted += WorkerFinished;
w.WorkManager = this;
w.BackgroundWorker = bg;
bg.RunWorkerAsync(w);
return w;
}
public void WorkerFinished(object sender, RunWorkerCompletedEventArgs e)
{
if (_onManagerEvent != null)
_onManagerEvent(new ManagerEvent { EventDate = DateTime.Now, Message = "Worker ??? successfully ended." });
}
So when my Worker object finishes the loop in its Start() method, what do I do to fill the userState property of the RunWorkerCompleteEventArgs object "e" that is passed to my WorkerFinished method()?
Thanks
Your Start method on the Worker class can set the Result property of the DoWorkEventArgs argument. Here's an example:
void Start(object sender, DoWorkEventArgs e)
{
//Do your loop and other work.
e.Result = this;
}
Then in the finish event handler, you can retrieve e.Result:
public void WorkerFinished(object sender, RunWorkerCompletedEventArgs e)
{
//You should always check e.Cancelled and e.Error before checking e.Result!
// ... even though I'm skipping that here
Worker w = e.Result as Worker;
if( w != null)
{
if (_onManagerEvent != null)
_onManagerEvent(new ManagerEvent
{
EventDate = DateTime.Now,
Message = String.Format("Worker {0} successfully ended."
, w.ToString())
});
}
}
That UserState thing is a known bug in BackgroundWorker:
http://www.pluralsight-training.net/community/blogs/mike/archive/2005/10/21/15783.aspx (archive.org link…original link is dead)
What I've done in the past when I've been in your situation is either use RunWorkerCompletedEventArgs.Result (as Philip suggests), or, if possible, have my worker derive from BackgroundWorker (then I can add as much extra state as I want, and get the whole worker as the sender argument to the events raised by BackgroundWorker, while still being able to use Result for its intended purpose).
Fifteen years later the bug mentioned in lesscode's answer has not been fixed, even after Microsoft ported Winforms to .NET Core. I had the additional requirement that I needed to get the "user object" when the worker was canceled.
Because you can't set that user object and you can't access the result when the worker is canceled, I had to keep track of cancellation separately from the RunWorkerCompletedEventArgs implementation.
Here is my solution. First, create a result object that does double duty (sorry SRP) as a user object—something like
class WorkerStateAndResult
{
public bool Errored { get; set; }
public bool Canceled { get; set; }
// other state/results...
}
Then, in your worker's handler, immediately set the Result property to an instance of WorkerStateAndResult. Inside your handler, you will not set the DoWorkEventArgs.Canceled to true when the worker is canceled; you will instead set the property on your state object; the same is true for error cases. Your handler ends up looking something like
private void HandleWorkerDoWork(object sender, DoWorkEventArgs e)
{
var stateAndResult = new WorkerStateAndResult(...);
e.Result = stateAndResult;
try
{
// do the work and check for cancellation. if cancellation happens, set Canceled
// instead of using built-in e.Canceled property
stateAndResult.Canceled = ResultOfActualWork();
}
catch
{
// handle errors, again, not using built-in mechanism
stateAndResult.Error = true;
}
finally
{
// any cleanup
}
}
Finally, in the RunWorkerCompleted handler, you can access result, which has all of your results and state, and you can check the Error and Canceled properties to do whatever logic is needed:
private void HandleAnalyzerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// this would normally throw if error or canceled, but not anymore!
var result = (WorkerStateAndResult)e.Result;
if (result.Canceled)
{
// do canceled logic
}
else if (result.Errored)
{
// do error logic
}
else
// do regular logic using result
}

Categories