How to stop BackgroundWorker by specific message in ReportProgress - c#

In the example below, I use the ReportProgress functionality of the BackgroundWorker:
void MyMethod()
{
if (!File.Exists)
{
bw.ReportProgress(0, "Error");
}
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.MyMethod1();
this.MyMethod2();
this.MyMethod3();
}
private void bw_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
string error = e.UserState as String;
if (!string.IsNullOrEmpty(error)
{
// stop BackgrowndWorker
}
}
In every MyMethod() i check something and do ReportProgress.
If there are possibilities to correctly stop BackgroundWorker in my case?
Need i to change the way of my code architecture?

The ReportProgress method is to... report progress. The only thing you can pass in is an percentage and the user state. That's all. You shouldn't use it to pass in an error.
Instead, the best thing you can do is to throw an exception. This will bubble up till the RunWorkerCompleted event handler, which has an event argument property named Error. Check that property in the event handler you write and you know what went wrong.

Related

Find as you type error backgroundworker in C# winform

I have the following form where I am trying to implement an incremental search on, using a backgroundworker.
So the idea is the user types in the textbox at the top, and for each keystroke, the listview below is filtered to contain only the items that contain the characters the user has typed.
I have recently learnt about the backgroundworker component and was therefore trying to use it to do the filtering and updating the listbox.
This is the event code for the textbox:
private void txtSearch_TextChanged(object sender, EventArgs e)
{
if (!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
}
}
and the backgroundworker event is:
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
if (txtSearch.Text != String.Empty)
{
GetTheListOfFiles();
listView.Items.Clear(); << Exception occurs here !
...... //some more code to populate the listview control
}
}
PROBLEM
When I type into the textbox, I was expecting the listbox to respond immediately to my keystrokes and display the filtered data accordingly. Instead, there is a pause of about 8 seconds and then I get this error:
I presume the issue is the bit that I have highlighted, but I have no idea how to solve it. Is it that a backgroundworker cannot be used for this purpose or am I missing something in my implementation?
PS: I welcome any different way to accomplish this. Perhaps there's a better solution out there among more experienced programmers?
UPDATE
Here is the progresschanged event I am using:
private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
toolStripProgressBar1.Value = e.ProgressPercentage;
tsLabelTwo.Text = e.ProgressPercentage.ToString() + #"%";
}
Thanks
If you create a control using the UI thread, you can't access it thought another thread (eg some background thread)
Just invoke the block that is throwing cross-thread exception on the main thread:
listView.BeginInvoke(new Action(() => { listView.Items.Clear(); }));
If you want to update UI, you need to invoke the control:
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
if (txtSearch.Text != String.Empty)
{
GetTheListOfFiles();
listView.Dispatcher.BeginInvoke(new Action(() => listView.Items.Clear()), DispatcherPriority.Background);
}
}
This is because you are trying to a control that runs on UI thread from another thread you've created, which is considered illegal. The correct workaround for this is to invoke your control, in this case your ListView.
listView.BeginInvoke(new Action(() =>
{
listView.Items.Clear();
//or perform your UI update or whatever.
}));
But if you wanna be such a rebel and do illegal stuff (sarcasm), add this piece of code right after your InitializeComponents(); method in the form's constructor.
Control.CheckForIllegalCrossThreadCalls = false;
But don't, there is a reason it is called "Illegal Thread Calls" :)
For more information Control.CheckForIllegalCrossThreadCalls Property

Accessing functions across threads

I am working on a program which uses a backgroundWorker to append text to a Textbox control. My problem is that simply, the backgroundWorker will not insert text into the Textbox control, it just remains blank.
My code:
private void button1_Click(object sender, EventArgs e) {
backgroundWorker1.RunWorkerAsync(); //Start the worker
}
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
this.writeText("Hello World!");
}
public void writeText(string text) {
textbox1.Text = textbox1.Text + text + "\r\n";
textbox1.SelectionStart = textbox1.Text.Length;
textbox1.ScrollToCaret(); //Scroll to the end of the textbox
}
Looking at the code, it seems fine (to me anyway) and it compiles fine too, so it's probably something very obvious that I am missing.
If someone would care to enlighten me on what I am doing wrong, it would be much appreciated ;)
In the method subscribed to the DoWork event, do your long-running operation. Then, within the worker method, you can call ReportProgress to send updates. This causes the OnProgressChanged event to fire on your UI thread, at which time you can make changes to your UI.
Alternately, if you're on .NET 4.5, you could use the async/await pattern to keep your UI responsive while performing long-running, I/O-bound operations. For CPU-bound operations, a BackgroundWorker is still appropriate.
As always, MSDN is a fantastic resource. From the BackgroundWorker page:
To set up for a background operation, add an event handler for the
DoWork event. Call your time-consuming operation in this event
handler. To start the operation, call RunWorkerAsync. To receive
notifications of progress updates, handle the ProgressChanged event.
To receive a notification when the operation is completed, handle the
RunWorkerCompleted event.
You must be careful not to manipulate any user-interface objects in
your DoWork event handler. Instead, communicate to the user interface
through the ProgressChanged and RunWorkerCompleted events.
You can declare a delegate at the class level for your Form.
public delegate void WriteLogEntryDelegate(string log_entry);
You can then wrap up most of the logic in another method:
void WriteLogEntryCB(string log_entry)
{
if (textbox1.InvokeRequired == true)
{
var d = new WriteLogEntryDelegate(WriteLogEntryCB);
this.Invoke(d, log_entry);
}
else
{
textbox1.Text(log_entry + "\r\n");
this.textbox1.SelectionStart = this.textbox1.Text.Length;
this.textbox1.ScrollToCaret();
}
}
You can then call that Function from your DoWork Method:
public void DoWork(object sender, DoWorkEventArgs e)
{
WriteLogEntryCB("Hello World!");
}
Edit to Include Daniel Mann's Suggestion:
Another way would be to cast the sender in the DoWork method as the BackgroundWorker, and then call the ReportProgress Method (or use the RunWorkerCompleted Event Handler).
void bw1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
worker.ReportProgress((1));
}
You would then require an event handler for the ProgressChanged Event:
void bw1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//update textbox here
}
Finally, you could also use the RunWorkerCompleted Event Handler:
void bw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//update textbox here
}
In the writeText method, the UI related code should be invoked by the UI
thread that create the UI controls.
What you do is called attempt to get access to the UI element from other the thread rather than the thread where element was created.
Read more info here.

LoadCompleted event in DoWork event

First of all, I post my code:
public partial class MainWindow : Window
{
private readonly BackgroundWorker worker = new BackgroundWorker();
private List<string> list = new List<string>();
private List<string> arrOfAdresses = new List<string>();
public MainWindow()
{
InitializeComponent();
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
list.Add("http://www.yahoo.com");
list.Add("http://www.google.com");
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("All is done");
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
foreach (string s in list)
{
WebBrowser bro = new WebBrowser();
bro.Width = bro.Height = 1;
grid.Children.Add(bro);
bro.Navigate(s);
bro.LoadCompleted += OnLoadCompleted;
}
}
private void button_Click(object sender, RoutedEventArgs e)
{
worker.RunWorkerAsync();
}
private void OnLoadCompleted(object sender, NavigationEventArgs e)
{
WebBrowser bro = sender as WebBrowser;
this.arrOfAdresses.Add(bro.Source.ToString()+"Added text");
MessageBox.Show("xxx"); //MessageBox is ignored
}
private void shower_Click(object sender, RoutedEventArgs e)
{
arrOfAdresses.Reverse();
foreach (string s in arrOfAdresses)
MessageBox.Show(s);
}
}
button_click event should store adresses into arrOfAdresse, which will be little bit modified.
Next, shower_Click should show all modified values which are in arrOfAdresses. When I want to show values, which I shoud have in arrOfAdresses, it returns me nothing. I think, problem is in LoadCompleted event, because when I put the MessageBox the program ignore it. Is there some way I can show values, when shower_Click is raised and is there some way I can fix it? Thank you for replies.
DoWork runs on another thread so you're not allowed to touch the UI (directly). Most obvious offender:
grid.Children.Add(bro);
But creating and loading the WebBrowser is probably not OK either.
Most important lesson to learn here:
always check the e.Error property first in a Completed event.
There's no way the message box is just ignored. Most likely the line above it threw an exception. WPF doesn't crash the application when exceptions are thrown, instead he logs them to the Debug output. Look there for the exception and you'll know what's wrong.
Most likely the exception is thrown because the line doesn't occur in the UI thread. If that's the case, all you need to do is run the command using a dispatcher, like this:
Dispatcher.Invoke(new Action(() => arrOfAdresses.Add(bro.Source.ToString() + "Added text")));
Note the dispatcher in my sample is a property of window: http://msdn.microsoft.com/en-us/library/system.windows.dependencyobject.dispatcher(v=vs.95).aspx
I have done things like this in Webbrowser. Loading progress in webbrowser in actually done in a seperate thread even if u call it from the main thread. So in my case, in many situations, if there is any error in between any of the lines in onLoadComplete or onPregress events, the error is not thrown. I donno how or why. But what i'll do is just debug. You have to put breakpoint right into starting line of the onLoadComplete event, and analyse line by line.. Even Try Catch wont give result, but this does.. And at the line where the program skips the next lines will be havin error..
There will be an error in the line
this.arrOfAdresses.Add(bro.Source.ToString()+"Added text");

Trigger control's event programmatically

Assume that I have a WinFoms project. There is just one button (e.g. button1).
The question is: is it possible to trigger the ButtonClicked event via code without really clicking it?
Button controls have a PerformClick() method that you can call.
button1.PerformClick();
The .NET framework uses a pattern where for every event X there is a method protected void OnX(EventArgs e) {} that raises event X. See this Msdn article. To raise an event from outside the declaring class you will have to derive the class and add a public wrapper method. In the case of Button it would look like this:
class MyButton : System.Windows.Forms.Button
{
public void ProgrammaticClick(EventArgs e)
{
base.OnClick(e);
}
}
You can just call the event handler function directly and specify null for the sender and EventArgs.Empty for the arguments.
void ButtonClicked(object sender, EventArgs e)
{
// do stuff
}
// Somewhere else in your code:
button1.Click += new EventHandler(ButtonClicked);
// call the event handler directly:
ButtonClicked(button1, EventArgs.Empty);
Or, rather, you'd move the logic out of the ButtonClicked event into its own function, and then your event handler and the other code you have would in turn call the new function.
void StuffThatHappensOnButtonClick()
{
// do stuff
}
void ButtonClicked(object sender, EventArgs e)
{
StuffThatHappensOnButtonClick();
}
// Somewhere else in your code:
button1.Click += new EventHandler(ButtonClicked);
// Simulate the button click:
StuffThatHappensOnButtonClick();
The latter method has the advantage of letting you separate your business and UI logic. You really should never have any business logic in your control event handlers.
Yes, just call the method the way you would call any other. For example:
private void btnSayHello_Click(object sender, EventArgs e)
{
MessageBox.Show("Hello World!");
}
private void btnTriggerHello_Click(object sender, EventArgs e)
{
btnSayHello_Click(null, null);
}
button1.PerformClick();
But if you have to do something like this maybe it's better to move the code you have under the event on a new method ?
Why don't you just put your event code into a Method. Then have the Event execute the method. This way if you need to execute the same code that the Event rises, you can, but simply just calling the "Method".
void Event_Method()
{
//Put Event code here.
MessageBox.Show("Hello!");
}
void _btnSend_Click(object sender, EventArgs e)
{
Event_Method();
}
void AnotherMethod()
{
Event_Method();
}
Make sense? Now the "Click" event AND anywhere in code you can trigger the same code as the "Click" event.
Don't trigger the event, call the method that the event calls. ;)
In most cases you would not need to do that. Simply wrap your functionality in functions related to a specific purpose (task). You call this function inside your event and anywhere else it's needed.
Overthink your approach.
I recently had this problem where I wanted to programatically click a button that had multiple event handlers assigned to it (think UserControl or derived classes).
For example:
myButton.Click += ButtonClicked1
myButton.Click += ButtonClicked2;
void ButtonClicked1(object sender, EventArgs e)
{
Console.WriteLine("ButtonClicked1");
}
void ButtonClicked2(object sender, EventArgs e)
{
Console.WriteLine("ButtonClicked1");
}
When you click the button, both functions will get called. In the instances where you want to programmatically fire an event handler for a function from a form (for example, when a user presses enter in a Text field then call the InvokeOnClick method passing through the control you. For example
this.InvokeOnClick(myButton, EventArgs.Empty);
Where this is the Form instance you are in.
use a for loop to call the button_click event
private void btnadd_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i <= 2; i++)
StuffThatHappensOnButtonClick();
}
void StuffThatHappensOnButtonClick()
{
........do stuff
}
we assume at least one time you need click the button

Is The reuse of the backgroundworker object possible?

I have a button "refresh" which every time i click on it i want my backgroundworker object to work.
i use
if (main_news_back_worker.IsBusy != true)
{
// Start the asynchronous operation.
main_news_back_worker.RunWorkerAsync();
}
private void main_news_back_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
show_system_urls(urls);
displayNewMes(newMes, newStock, newSource);
displayOldMes(oldMes, oldStock);
}
The first time i use the backgroundworker it work good and also get to the RunWorkerCompleted and do his work.
But the second time i try to run the object the is_busy property of the object is 'true' and i cant run the object again...
Do i need to create a new backgroundworker every time i want to run it? how do i do it?
Thanks.
Yes, no problem. You will however have to make sure that the user cannot click the button again while the BGW is busy. Easily done by setting the Enabled property, stops the button action and provides excellent visual feedback to the user. Try this for example:
private void button1_Click(object sender, EventArgs e) {
button1.Enabled = false;
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
System.Threading.Thread.Sleep(2000);
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
button1.Enabled = true;
}
But the second time i try to run the object the is_busy property of the object is 'true'
That means the first background action is still running.
You will first have to decide if you want 2 of these actions to be going on at the same time.
If No, implement Cancellation so that you can Stop (and then restart) the Bgw.
If Yes, create a new Bgw each time.
And while you can re-use a Bgw, and that makes sense in the 1st scenario, there is no great saving in doing so. The Bgw Thread comes from the ThreadPool and will be re-used anyway.

Categories