Multithreading with events in WPF - c#

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.

Related

WPF SelectionChanged event do the action before tab change

i have SelectionChanged event in my WPF aplication. i want that when the tab is change to do some action but first i want the tab to visualy change before the action starts. i am using background worker to do the job. my code is:
private void Tab_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (tab1.IsSelected)
{
//this line is not working
tabcontrol.SelectedIndex = 1;
wNetTest = new BackgroundWorker();
wNetTest.DoWork += new DoWorkEventHandler(worker_DoWork);
wNetTest.RunWorkerCompleted += worker_RunWorkerCompleted;
wNetTest.WorkerReportsProgress = true;
wNetTest.WorkerSupportsCancellation = true;
wNetTest.RunWorkerAsync();
}
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
//do the job
}
Your problem is that your code is running synchronously. Therefore, every line of your Tab_SelectionChanged event handler will run before you will see the TabItem change. To fix this problem, you just need to run your long running process asynchronously. One of the simplest ways to do that is this:
private void Tab_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (tab1.IsSelected)
{
//this line is not working
tabcontrol.SelectedIndex = 1;
Task.Factory.StartNew(() => LongRunningMethod(parameter));
}
}
private void LongRunningMethod(object parameter)
{
// perform long running process here
}
The parameter input parameter is optional... just remove it if you don't need it.

Updating the UI right before running the BackgroundWorker

I have this really little problem, but which can't be easily solved. Currently, my program has 2 buttons, a "Start" and a "Cancel". When the user clicks the start, the buttons should go instantly:
StartButton.IsEnabled = false;
CancelButton.IsEnabled = true;
But this occurs only when the BackgroundWorker has finished (all the code which will be ran after pressing the button), because the UI is always updated as last. There's no way I could add these commands to the "ProgressChanged" or "Completed" event of the backgroundworker. These events can take up to 10min to complete.
One easy way is to add these commands to the "ProgressChanged" part, and in the end "Complete" change their state again. But I'd like to avoid this, as the buttons should be showing their real state all the time, not after few "ProgressChanged" events. Of course there's always ways around, like not using the button's UI properties.
Is there any short solution for this?
It doesn't work to add the Button.Property changes to the ClickEvent. That's the main problem in this. I can easily use the "Completed" part of BGW to change the Button's back to match the starting state. The problem is to get them set right before all the events and BGW.
if you have a start button like:
this.StartButton = new System.Windows.Forms.Button();
then you can do
this.StartButton.Click += new System.EventHandler(this.button1_Click);
and then do
private void button1_Click(object sender, EventArgs e)
{
StartButton.IsEnabled = false;
CancelButton.IsEnabled = true;
Thread bg = new Thread(new ThreadStart( UpdateDatabase()));
bg.Start();
}
if you want the bg thread to send messages to the UI use the Invoke method like here
public delegate void UpdateUIHndler();
public void UpdateUI()
{
}
and do
if (InvokeRequired)
{
Invoke(new UpdateUIHndler(UpdateUI));
}
Take a look at a previous question of mine (quite similer). I should go for option 1.
ASP.NET Application log while code is running / progress bar
UI will only be delayed 5 seconds. Instead of text update the button styling using AJAX.
you can disable the start button in the click event of that button itself and enable it again it on RunWorkerCompleted event of BGW as shown below
BackgroundWorker _worker = new BackgroundWorker();
_worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
private void StartButton_Click(object sender, RoutedEventArgs e)
{
startButton.IsEnabled = false;
}
void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
stratButton.IsEnabled = true;
}
void _worker_DoWork(object sender, DoWorkEventArgs e)
{
//Your processing code
}

Event not raising properly in forms

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...

In my dll, I expose logging msgs by an event. How can I properly update a listbox on my form with log msgs?

In my dll I expose an event like so:
public delegate void LogMsgEventHandler(object sender, LogEventArgs e);
public event LogMsgEventHandler newLogMessage;
public class LogEventArgs : EventArgs
{
public string logMsg;
}
protected virtual void OnChanged(LogEventArgs e)
{
if (newLogMessage != null)
newLogMessage(this, e);
}
The various methods fire the event to log operation details. On my main form that uses the dll I output any log msgs to a listbox:
slotUtil.newLogMessage += new slotUtils.LogMsgEventHandler(slotUtil_newLogMessage);
..
void slotUtil_newLogMessage(object sender, slotUtils.LogEventArgs e)
{
lbDebug.Items.Add(e.logMsg);
}
The problem is if the logging event is fired too rapidly, the form freezes up. I assume this is a threading problem? How can I fix this design where the form updates fluidly? Is this a bad design? My alternate design id was too store all logging in a private string in the dll and then only dump the log when a particular method is called. Thoughts?
Thanks!
It is not a Threading issue if instances of classes from your dll aren't in another thread. The event handler
void slotUtil_newLogMessage(object sender, slotUtils.LogEventArgs e)
{
lbDebug.Items.Add(e.logMsg);
}
executes in the same thread where it is registered.
So if your instances are in the UI thread this event handler is executing in the UI thread also. It adds new item to the ListBox which triggers new listbox redraw. If events are firing faster than the drawing occurs, then event handlers will pile up in a queue waiting for Invalidates to finish. If this is the case you should try to, at least do something like this:
System.Windows.Forms.Timer t;
public Form1()
{
InitializeComponent();
t = new System.Windows.Forms.Timer();
t.Interval = 1000;
t.Tick += new EventHandler(t_Tick);
}
void t_Tick(object sender, EventArgs e)
{
for(int i = 0;i<count;i++)//you must add here only those valid strings, can't use foreach
lbDebug.Items.Add(item);
count = 0;
}
static int count = 0;
static string[] items = new string[5];
void slotUtil_newLogMessage(object sender, slotUtils.LogEventArgs e)
{
t.Stop();
items[count++] = e.logMsg;
if (count >= items.Length)
{
foreach (string item in items)
lbDebug.Items.Add(item);
count = 0;
}
else
{
t.Start();
}
}
The adding of the items to the listbox will happen either when five logs are ready or a second has elapsed after last log. You can add whatever number of logs in the array (just change it's size) before they are added to the listbox.

Opening A WinForm Programmatically In Another WinForm's Code

Happy Friday SO!
I'm building a multi-WinForm application and am having some troubles.
I have a main WinForm that stays open at all times. It hits a database every minute looking for changes, and if there is a change, it will open the second WinForm (this may seem like a stupid way to do things, but for my purpose, this is the best method).
Just sticking the following code into my Form1.cs doesn't do the trick:
Application.Run(new Form2());
Can you guys point me in the right direction? I have no idea where to turn.
Form2 form2 = new Form2();
form2.Show();
and to prevent a ton of forms being opened, maybe:
Form2 form2 = new Form2();
form2.ShowDialog();
#Comment:
A BackgroundWorker is used to keep your current UI Thread responsive. It was not designed to keep multiple forms pumping happily along. Look into running your intensive code as a Background thread within a ThreadPool.
If what you wish is to launch a long process and to show the progress to the user, for example just like when you have a progress bar or something alike, you should use a BackgroundWorker to do the job. Here's a simple example:
public partial class ProgressForm : Form {
// Assuming you have put all required controls on design...
// Allowing some properties to be exposed for progress update...
public properties MaximumProgress {
set {
progressBar1.Maximum = value;
}
public properties OverallProgress {
set {
progressBar1.Value = value;
}
}
public partial class MainForm : Form {
private BackgroundWorker backgroundWorker1;
private ProgressForm _pf;
public MainForm() {
InitializeComponent();
backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
}
// Assuming process starts on Button click.
private void button1_Click(object sender, EventArgs e) {
_pf = new ProgressForm();
_pf.MaximumProgress = number-of-elements-to-treat-returned-by-prevision-or-whatever-else;
// Launching the background workder thread.
backgroundWorker1.RunWorkerAsync(); // Triggering the DoWork event.
// Then showing the progress form.
_pf.ShowDialog();
}
private void backgroundWorker1_DoWork(object sender, EventArgs e) {
LaunchProcess();
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
_pf.OverallProgress = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, EventArgs e) {
_pf.Close();
_pf.Dispose();
}
private void LaunchProcess() {
// Do some work here...
// Reporting progress somewhere within the processed task
backgroundWorker1.ReportProgress();
}
}
This is not a compileable code as its purpose is to illustrate the main idea.
Now, is this something alike you want to do?

Categories