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");
Related
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
UPDATE:
As mentioned in the comments section the problem was solved, but I do not understand why my way of implementation was wrong.
I have this situation:
I have a device which can be triggered with an event in my WPF project.
This event pulls Data from my device at a polling rate of 1ms. I want to process
the data in different threads.
My approach was, to start a backgroundworker which registers the device event (I read events run on the thread they are called from). In the device event itself the data is saved to an object, which is declared in my form. After that the labels in the WPF form a are refreshed with a Invoke Method.
This happens until someone presses cancel on a button in my form, which unregisters the device event and stops the thread.
Here is some code I use:
Declaration in my Main Window:
public partial class MainWindow : Window
{
private BackgroundWorker worker = new BackgroundWorker();
private MeasureObject mObject = new MeasureObject();
... }
This is my initialization:
public MainWindow()
{
InitializeComponent();
this.worker.WorkerSupportsCancellation = true;
this.worker.DoWork += worker_DoWork;
this.worker.RunWorkerCompleted += worker_RunWorkerCompleted;
}
If this button is press i run my background worker:
private void btnStartMeasure_Click(object sender, RoutedEventArgs e)
{
this.worker.RunWorkerAsync();
}
Here I register my event for the device. It should now run on my worker thread. I tried to declare the event itself here too, but it did not work, so I placed it in my main windows.
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.myController.ControlCenter.Diagnostics.NewDiagPacketArrived += new EventHandler<NewDiagPacketArrivedEventArgs>(Diagnostics_NewDiagPacketArrived);
// run all background tasks here
}
This is not needed and empty. The worker is only cancelled if the user sets it on cancel.
private void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
}
This event is triggered in my Window and calls two functions, it should run on the backgroundworker if I am correct.
private void Diagnostics_NewDiagPacketArrived(object sender, NewDiagPacketArrivedEventArgs e)
{
try
{
Measure(e);
this.Dispatcher.BeginInvoke( new Action(() => { SetStates(e); }),System.Windows.Threading.DispatcherPriority.Input);
}
catch
{
}
}
Measure gets the e Object from the device and saves it to a Dataobject i created
private void Measure(NewDiagPacketArrivedEventArgs e)
{
lock(this.mObject)
{
this.mObject.ID = this.list.Count;
....
this.list.Add(this.mObject);
}
}
SetStates refreshed the GUI
private void SetStates(NewDiagPacketArrivedEventArgs e)
{
lock(this.mObject)
{
this.lblID.Content = this.mObject.ID;
}
}
The problem with my code is if I cancel the event and the thread with this code:
private void btnStopMeasure_Click(object sender, RoutedEventArgs e)
{
this.myController.ControlCenter.Diagnostics.NewDiagPacketArrived -= Diagnostics_NewDiagPacketArrived;
this.worker.CancelAsync();
}
And try to get the list where I added my objects, all objects have the same ID's and values. It seems like as soon as I unregister the event or press the stop measure button, all mObjects in my list get overwritten with the mObject at the time when I unregister the event.
so my list looks like this:
list[0].ID = 1
list[1].ID = 1
list[2].ID = 1
rather than this:
list[0].ID = 1
list[1].ID = 2
list[2].ID = 3
Maybe you can help?
Your problem is that you are not creating a new instance of the mObject - you only create one of them here:
private MeasureObject mObject = new MeasureObject();
Your code then adds the SAME object to the list, and updates that. You need to make a new object each time and put it in the list.
this.mObject.ID = this.list.Count;
....
this.list.Add(this.mObject);
Currently your list is a list of the same object.
I have a MainWindow with eventhandler which is not working properly. I have made simple model of this problem. Please see comment in code where the problem is:
public partial class MainWindow : Window
{
public event EventHandler Event1;
public MainWindow()
{
Event1 += MainWindow_Event1;
InitializeComponent();
}
void MainWindow_Event1(object sender, EventArgs e)
{
textBox1.Text = "wth!?"; //Not changing text box. Not showing message. If delete this line, it will work fine
MessageBox.Show("raised");
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
EventHandler evt = Event1;
while (true)
{
Thread.Sleep(500);
evt(null, null);
}
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerAsync();
}
}
Please explain this behavior and how can I fix it?
The problem is that you're invoking the event from a background thread. This will not work and the program is simply hanging when trying to access the TextBox. However, if you change this code:
textBox1.Text = "wth!?"; //Not changing text box. Not showing message. If delete this line, it will work fine
MessageBox.Show("raised");
to this:
this.Dispatcher.BeginInvoke((Action)delegate()
{
textBox1.Text = "wth!?"; //Not changing text box. Not showing message. If delete this line, it will work fine
MessageBox.Show("raised");
});
it'll work for you.
You can't update the UI elements from the background thread.
The worker thread fails by exception trying to access the UI element (Text property). So messageBox isn't showing as well. Use notification mechanisms, or Dispatcher calls (there is a wast amount of information like this on the web)
Here are possible duplicates/help:
Update GUI using BackgroundWorker
Update GUI from background worker or event
This problem is because you need to use the Synchronization Context of the current Thread for comunicating between threads, some thing like this
private void Button_Click(object sender, RoutedEventArgs e)
{
var sync = SynchronizationContext.Current;
BackgroundWorker w = new BackgroundWorker();
w.DoWork+=(_, __)=>
{
//Do some delayed thing, that doesn't update the view
sync.Post(p => { /*Do things that update the view*/}, null);
};
w.RunWorkerAsync();
}
Please check this question, hope can helps...
Lets say I have Task 1:
private void Task1()
{
//Here is some Code, could be any "longer" Task -
//For Example: Grab all words from a .txt File and fill in a List<String>
}
Then I have an other Task 2:
private void Task2(string word)
{
//So lets say theres a Label on my WinForm..
//Now While Task1 is grabbing the words, Task2 should fill a Label
//with the added 'word' (parameter) - (Task2 will be called from Task1
}
Actually I don't know how to make this possible, or whats the best way. On the UI I should be able to see the Label.Text changing (every word).. So I need to make a second Thread? How could I do this? Maybe someone could help me, cheers
UPDATE:
I tried it now with the Backgroundworker, but something seems to be false.. its actually not working, nothing happens on the form
Code:
public void CreateAndSaveAMatch(DateTime date) //That method is being called several times
{
//HERE IS CODE, WHICH CREATES AND SAVES A MATCH
// Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync(date);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
backgroundWorker1.ReportProgress(0, Convert.ToDateTime(e.Argument).ToShortDateString());
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = (string)e.UserState; //here on the Label I would like to show the Date
}
Ok, try this. This is a simple example that will show you how to solve your problem using BackgroundWorker. Also note that there are many other solutions. To use this example create a Form in a new project that only has a button and a label. Also note that this is a supplement of the other answers which were correct.
public partial class Form1 : Form
{
BackgroundWorker createAndSaveAMatchBGW;
public Form1()
{
InitializeComponent();
createAndSaveAMatchBGW = new BackgroundWorker();
createAndSaveAMatchBGW.DoWork += new DoWorkEventHandler(createAndSaveAMatchBGW_DoWork);
createAndSaveAMatchBGW.ProgressChanged += new ProgressChangedEventHandler(createAndSaveAMatchBGW_ProgressChanged);
createAndSaveAMatchBGW.RunWorkerCompleted += new RunWorkerCompletedEventHandler(createAndSaveAMatchBGW_RunWorkerCompleted);
createAndSaveAMatchBGW.WorkerReportsProgress = true;
}
private void button1_Click(object sender, EventArgs e)
{
createAndSaveAMatchBGW.RunWorkerAsync(DateTime.Now);
}
void createAndSaveAMatchBGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("BackgroundWorker finished");
}
void createAndSaveAMatchBGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = ((DateTime)e.UserState).ToString("ss");
}
void createAndSaveAMatchBGW_DoWork(object sender, DoWorkEventArgs e)
{
//BackgroundWorker does something for a 10 seconds, each second it Reports
BackgroundWorker bgw = (BackgroundWorker)sender;
DateTime dt = (DateTime) e.Argument;
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
dt = dt.AddSeconds(1);
bgw.ReportProgress(0, dt);
}
}
}
And if you report from CreateAndSave... method only once per its execution, then you can use this code:
BackgroundWorker createAndSaveAMatchBGW;
public Form1()
{
InitializeComponent();
createAndSaveAMatchBGW = new BackgroundWorker();
createAndSaveAMatchBGW.DoWork += new DoWorkEventHandler(createAndSaveAMatchBGW_DoWork);
createAndSaveAMatchBGW.RunWorkerCompleted += new RunWorkerCompletedEventHandler(createAndSaveAMatchBGW_RunWorkerCompleted);
}
private void button1_Click(object sender, EventArgs e)
{
createAndSaveAMatchBGW.RunWorkerAsync(DateTime.Now);
}
void createAndSaveAMatchBGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
label1.Text = ((DateTime)e.Result).ToString();
}
void createAndSaveAMatchBGW_DoWork(object sender, DoWorkEventArgs e)
{
DateTime dt = (DateTime) e.Argument;
//you do something with your DateTime
dt = dt.AddDays(10);
e.Result = dt;
}
Use BackgroundWorker for reporting progress from first task. Drag this component from toolbox to your form, and subscribe to DoWork and ProgressChanged events. Also set property WorkerReportsProgress to true. Then start you first task asynchronously:
// this will execute code in `DoWork` event handler
backgroundWorker1.RunWorkerAsync();
Next - use userState object to pass processed words:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// grab words in a loop and report progress
backgroundWorker1.ReportProgress(0, word);
}
And last step - update label in ProgressChanged event handler
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text += (string)e.UserState; // this is your grabbed word
}
The simplest way to achieve this kind of thing is using BackgroundWorker.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
BackgroundWorker automatically handles thread marshalling and provides events that allow you to update the UI. The event handlers run on the UI thread.
The things you do in Task1 could be moved into a BackgroundWorker, and the updates to the UI that you propose to do in Task2 can actually be in response to progress events from BackgroundWorker.
ProgressChangedEventArgs provides for user-defined data that could hold the current word.
However, Winforms (and indeed pretty much any UI) will not be able to keep up with a separate CPU thread just loading words from a file if you intend to show every word you load.
Task1 could be started on a separate thread.
You wouldn't actually need a Task2 unless there was some complex logic being performed to update the TextBox. You you really need to do is use TextBox.Invoke() to invoke the update on the UI Thread from Task1.
ObservableCollection<String> listBoxItems = new ObservableCollection<String>();
scheduledRecordingListBox.ItemsSource = listBoxItems;
public void timerElapsed(object sender, ElapsedEventArgs e)
{
listBoxItems.Remove(itemToBeRemoved);
}
Just a snippet of what I'm actually trying to do. I believe the error is caused because the timer is running on a different thread than the GUI main thread that the ObservableCollection I'm trying to remove from is.
If you are using WinForms, then just use the System.Windows.Timer class. It's Tick event is automatically executed on the UI thread.
This should do the Trick:
public void timerElapsed(object sender, ElapsedEventArgs e)
{
this.Invoke(new Action(() => listBoxItems.Remove(itemToBeRemoved)));
}
Try using Invoke it executes a delegate on the thread that owns the control's underlying window handle.
You can also have a look of the section timers in this page