Data Binding on multi thread application? - c#

The following code is the simplified version of my problem:
public partial class Form1 : Form
{
BackgroundWorker bw;
Class myClass = new Class();
public Form1()
{
InitializeComponent();
bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
label1.DataBindings.Add("Text", myClass, "Text", true, DataSourceUpdateMode.Never);
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
myClass.Text = "Hi";
}
private void button1_Click(object sender, EventArgs e)
{
bw.RunWorkerAsync();
}
}
public class Class : INotifyPropertyChanged
{
private string _Text;
private void SetText(string value)
{
if (_Text != value)
{
_Text = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string Text
{
get { return _Text; }
set { SetText(value); OnPropertyChanged(new PropertyChangedEventArgs("Text")); }
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, e);
}
}
What happens is that when I click the button1 (which invokes button1_Click) the text in label1 doesn't update. This is because the label1.Text property is internally trying to be changed from my BackgroundWorker's thread, which internally causes an exception. What would it be the best way, in general, to fix this kind of problem? Which part of this code would you change if you must update my Class.Text property from a different thread but must also have a control binded to it?

Try this:
//This is the handler to execute the thread.
void DoWork(object sender, EventArgs a) {
Action updateControl = ()=>myClass.Text = "Blah";
if(myForm.InvokeRequired) {
myForm.Invoke( updateControl);
}
else {updateControl();}
}
this routine executes on the background worker thread.

Fundamentally, you'll have to use either Invoke or BeginInvoke from within your worker thread to perform the actual update. It's illegal to interact directly with a control from within a thread other than the main UI thread (unless it's been written to account for this). With lambda syntax, using Invoke or BeginInvoke is somewhat cleaner than before. For example:
void bw_DoWork(object sender, DoWorkEventArgs e)
{
Invoke(new Action(() => myClass.Text = "Hi"));
}
This will execute the code on the UI thread. However, the BackgroundWorker class (which I'm assuming is what you're using, based on the names here) has a ReportProgress function, which raises the ProgressChanged event on the UI thread. It uses the same mechanism that I described above, but it might be a little cleaner looking. The choice is yours really.

Only the UI thread is allowed to change properties of UI controls. Your BackgroundWorker shouldn't be doing it.
Fortunately BackgroundWorker supports a ProgressChanged event. Your DoWork should raise that, and THAT can update the UI because the backgroundworker will marshal it back to the UI thread for you.

Related

GUI Invoke with EventHandlers

I have a class like that "ClientSocket.cs"
class ClientSocket {
public delegate void ConnectHandler(object sender, EventArgs e);
public event ConnectHandler ConnectEvent = delegate { };
protected void OnConnectEvent(EventArgs e) {
ConnectHandler ev = ConnectEvent;
ev(this, e);
}
}
And another class "myForm.cs"
public partial class myForm : Form {
private ClientSocket client;
private void button1_Click(object sender, EventArgs e) {
client = new ClientSocket();
client.ConnectEvent += myForm_OnConnectEvent;
client.connect();
}
// Handler for ConnectEvent
private void myForm_OnConnectEvent(object sender, EventArgs e) {
//this.BeginInvoke((MethodInvoker)delegate { writeLog("Connected"); });
writeLog("Connected");
}
// Function that write a log string to a TextBox
public writeLog(string log) {
guiTextBox.AppendText(log);
}
}
Here the question.
I try to call writeLog with BeginInvoke or calling it directly. Sometimes I get an InvalidOperationException when writing to guiTextBox.
I don't understand why I receive that message. The event is fired by ClientSocket object, but the event handler is relative to the main UI-thread (myForm).
Can I avoid to use BeginInvoke/Invoke for each EventHandler of my class?
EDIT: I understand what's the difference, now I'm try to understand the best practice for calling the event.
Should I put the BeginInvoke/Invoke method when RAISING the event in the BASE class (ClientSocket in that case)
protected void OnConnectEvent(EventArgs e) {
ConnectHandler ev = ConnectEvent;
this.BeginInvoke((MethodInvoker)delegate { ev(this, e);});
}
or should I put that WHEN I'm using that object and add a listeners to that handler
// Handler for ConnectEvent used in GUI (myForm)
private void myForm_OnConnectEvent(object sender, EventArgs e) {
this.BeginInvoke((MethodInvoker)delegate { writeLog("Connected"); });
}
Cheers
the this.BeginInvoke inside ClientSocket does not exist. To be able to do the BeginInvoke it must be called on a object that has that method (your form in your case).
If you wanted the invoking to happen inside your ClientSocket class you would need to pass in a Control that has the BeginInvoke function.
However if I where writing this I would not do this approach. It adds a unnecessary requirement to ClientSocket that you must have a Control passed in (this is called Tightly Coupling and you should try to avoid it in your programming). Personally I would let the event pass along in whatever thread it wants to be raised in and let the consumer worry about doing any special invoking (if they even need to at all).
Here is how I would write myForm_OnConnectEvent, this pattern checks to see if we need to invoke and if we do it calls the function again with the same arguments but this time on the UI thread.
// Handler for ConnectEvent used in GUI (myForm)
private void myForm_OnConnectEvent(object sender, EventArgs e)
{
if(this.InvokeRequired)
{
this.BeginInvoke(new ConnectHandler(myForm_OnConnectEvent), new object[] {sender, e});
return;
}
writeLog("Connected");
}
As a side note, I don't know what writeLog is doing (it should have a capital W by the way) but if it is not interacting with the UI you don't need to do any invoking at all. If it interacts with a TextBox or something else on the UI, that is where I would do my invoking.
private void myForm_OnConnectEvent(object sender, EventArgs e)
{
writeLog("Connected");
}
private void writeLog(string logMessage)
{
if(logTextBox.InvokeRequired)
{
logTextBox.BeginInvoke(new Action<string>(writeLog), logMessage);
return;
}
var logLine = String.Format("{0:g}: {1}{2}", DateTime.Now, logMessage, Enviorment.NewLine);
logTextBox.AppendText(logLine);
}
The event handler is declared in myForm, but the thread, which executes handler, is defined by the logic of ClientSocket class. If this will be background thread, event handler will be raised from background thread, so, you'll need BeginInvoke to avoid cross-thread access to controls.
In other words: belonging of any method of any type isn't related to the thread, which will ever execute this method. These things (types and threads) are parallel universes.
By the way, you can replace this:
public delegate void ConnectHandler(object sender, EventArgs e);
public event ConnectHandler ConnectEvent = delegate { };
with this:
public event EventHandler ConnectEvent;
There's no need to make yet another delegate type.

NullReferenceException happens when sending message back to message dialog from BackGroundWorker

It's a C# winform .Net framework 2.0 project: The time-consuming network transferring jobs run in a BackupgroundWorker. In this backgroundwork thread, SynchronizationContext method is used to send the current job title to a message dialog in the main UI thread. However, in my code, thread racing problem happens and "NullReferenceException" is given sometime (not always, but randomly). Here is my code:
private void DoBtn_Click(object sender, EventArgs e)
{
m_msgForm = new MsgForm(); //m_msgForm is a member variable of the class, and MsgForm is a form class with a "public static SynchronizationContext synContext"
m_msgForm.UpdateMsg("starting ..."); //UpdateMsg is public method to show progress information
BackgroundWorker myBackgroundWorker = new BackgroundWorker();
myBackgroundWorker.DoWork +=new DoWorkEventHandler(myBackgroundWorker_DoWork);
myBackgroundWorker.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(myBackgroundWorker_RunWorkerCompleted);
myBackgroundWorker.RunWorkerAsync(theBackgroundArgument);
m_msgForm.ShowDialog(); //show as a modal dialog
}
And in the Background worker thread:
private void myBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//... some code
string msgText ="doing job: " +job.Title;
RestoreMsgForm.synContext.Send(m_msgForm.UpdateMsg, msgText); //send message to m_msgForm in the UI thread
//... some code
}
The problem is : sometime the "RestoreMsgForm.synContext.Send() in the backgroundworker thread will run before m_msgForm.ShowDialog() in the UI thead.
And in this case, NullReferenceException will be throw.
How to solve this problem? thanks.
the definition of RestoreMsgForm is
public partial class RestoreMsgForm : Form
{
public static SynchronizationContext synContext;
public RestoreMsgForm()
{
InitializeComponent();
}
private void RestoreMsgForm_Load(object sender, EventArgs e)
{
synContext = SynchronizationContext.Current;
}
public void UpdateMsg(object msg)
{
msgLabel.Text = (string)msg;
}
}
See if it works like this instead:
private void DoBtn_Click(object sender, EventArgs e)
{
m_msgForm = new MsgForm(); //m_msgForm is a member variable of the class, and MsgForm is a form class with a "public static SynchronizationContext synContext"
m_msgForm.UpdateMsg("starting ..."); //UpdateMsg is public method to show progress information
BackgroundWorker myBackgroundWorker = new BackgroundWorker();
myBackgroundWorker.DoWork += new DoWorkEventHandler(myBackgroundWorker_DoWork);
myBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(myBackgroundWorker_RunWorkerCompleted);
myBackgroundWorker.WorkerReportsProgress = true;
myBackgroundWorker.ProgressChanged += new ProgressChangedEventHandler(myBackgroundWorker_ProgressChanged);
myBackgroundWorker.RunWorkerAsync(theBackgroundArgument);
}
private void myBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
worker.ReportProgress(-1);
//... some code
string msgText = "doing job: " + job.Title;
worker.ReportProgress(0, msgText);
//... some code
worker.ReportProgress(0, "...other text...");
//... some code
worker.ReportProgress(0, "...etc...");
}
void myBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == -1)
{
m_msgForm.ShowDialog(); //show as a modal dialog
}
else
{
m_msgForm.UpdateMsg(e.UserState.ToString);
}
}
*Note that we have to set .WorkerReportsProgress = true for the BackgroundWorker() so we can use ReportProgress() and receive the ProgressChanged() event.

using backgroundworker in Winforms (C#) with MVP pattern

I've been trying to refactor a spaghetti code of an app by using MVP pattern. But now I'm struggling with this:
A form that has button that calls a the DoWork method (of a backgroundworker) which is a long operation. My question is if I move the long operation out of the view into the Presenter then how do I send progress changes from this operation to the View? The BGW must be in the Presenter also?
Can you give me a sample of how to do this?
Thank you in advance.
This outlines the use of the BackgroundWorker:
private BackgroundWorker _backgroundWorker;
public void Setup( )
{
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.DoWork +=
new DoWorkEventHandler(BackgroundWorker_DoWork);
_backgroundWorker.ProgressChanged +=
new ProgressChangedEventHandler(BackgroundWorker_ProgressChanged);
_backgroundWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(BackgroundWorker_RunWorkerCompleted);
// Start the BackgroundWorker
_backgroundWorker.RunWorkerAsync();
}
void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// This method runs in a background thread. Do not access the UI here!
while (work not done) {
// Do your background work here!
// Send messages to the UI:
_backgroundWorker.ReportProgress(percentage_done, user_state);
// You don't need to calculate the percentage number if you don't
// need it in BackgroundWorker_ProgressChanged.
}
// You can set e.Result = to some result;
}
void BackgroundWorker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
// This method runs in the UI thread and receives messages from the backgroud thread.
// Report progress using the value e.ProgressPercentage and e.UserState
}
void BackgroundWorker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
// This method runs in the UI thread.
// Work is finished! You can display the work done by using e.Result
}
UPDATE
This BackgroundWorker has to be in the presenter of cause. The idea of patterns like MVP, MVC or MVVM is to remove as much code from the view as possible. The view would only have code very specific to the view itself, like creating the view or drawing in the Paint event handler and so on. Another kind of code in the view is the code necessary to communicate with the presenter or controller. The presenting logic, however, has to be in the presenter.
You would use the BackgroundWorker_ProgressChanged method that runs in the UI thread to send changes to the view. Either by calling public methods of the view or by setting public properties of the view or by exposing public properties the view can attach to by binding its properties or the properties of its controls to it. (This is borrowed from the MVVM pattern.) The presenter must implement INotifyPropertyChanged in order to notify the view that a property has changed, if you decide to bind the view to properties of the presenter.
Note: Another thread than the UI thread is not allowed to interact with the view directly (an exception is thrown if you try to do so). Therefore the BackgroundWorker_DoWork cannot interact with the view directly and therefore calls ReportProgress, which in turn runs BackgroundWorker_ProgressChanged in the UI thread.
You can place the BackGroundWorker in the presenter and add a method to the view to show the progress.
Something like this:
//Add a method to your view interface to show progress if you need it.
public interface IView
{
void ShowProgress(int progressPercentage);
}
//Implement method in the view.
public class MyView : Form, IView
{
public MyView()
{
//Assume you have added a ProgressBar to the form in designer.
InitializeComponent();
}
public void ShowProgress(int progressPercentage)
{
//Make it thread safe.
if (progressBar1.InvokeRequired)
progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Value = progressPercentage; }));
else
progressBar1.Value = progressPercentage;
}
}
// In your presenter class create a BackgroundWorker and handle it's do work event and put your time consuming method there.
public class MyPresenter
{
private BackgroundWorker _bw;
public MyPresenter()
{
_bw = new BackgroundWorker();
_bw.WorkerReportsProgress = true;
_bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
}
private void _bw_DoWork(object sender, DoWorkEventArgs e)
{
//Time consuming operation
while (!finished)
{
//Do the job
_bw.ReportProgress(jobProgressPercentage);
}
}
public void StartTimeConsumingJob()
{
_bw.RunWorkerAsync();
}
}
Don't forget to Dispose the BackgroundWorker when you're finished.
with your input I've managed to work this out. Please comment any flaws you may find with this approach:
* View interface *
public interface IView
{
void ShowProgress( int progressPercentage);
}
* View (a form) *
public partial class Form1 : Form, IView
{
MyPresenter p ;
public Form1()
{
InitializeComponent();
p = new MyPresenter(this);
}
private void button1_Click(object sender, EventArgs e)
{
if (p.IsBusy())
{
return;
}
p.StartTimeConsumingJob();
}
public void ShowProgress(int progressPercentage)
{
if (progressBar1.InvokeRequired)
progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Value = progressPercentage; }));
else
progressBar1.Value = progressPercentage;
}
private void button2_Click(object sender, EventArgs e)
{
p.Cancel();
}
}
* Presenter *
public class MyPresenter
{
private BackgroundWorker _bw;
private IView _view;
public MyPresenter(IView Iview)
{
_view = Iview;
_bw = new BackgroundWorker();
_bw.WorkerReportsProgress = true;
_bw.WorkerSupportsCancellation = true;
_bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
_bw.ProgressChanged += new ProgressChangedEventHandler(_bw_ProgressChanged);
_bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_Completed);
}
public void StartTimeConsumingJob()
{
_bw.RunWorkerAsync();
}
private void _bw_DoWork(object sender, DoWorkEventArgs e)
{
//Time consuming operation Do the job
Thread.Sleep(1000);
_bw.ReportProgress(50);
Thread.Sleep(2000);
if(_bw.CancellationPending)
{
e.Result = false;
}
}
public bool IsBusy()
{
return _bw.IsBusy;
}
public void Cancel()
{
_bw.CancelAsync();
}
private void _bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
_view.ShowProgress(e.ProgressPercentage);
}
private void _bw_Completed(object sender, RunWorkerCompletedEventArgs e)
{
if((bool)e.Result)
_view.ShowProgress(100);
else
_view.ShowProgress(0);
_bw.Dispose();
}
}

Set up Backgroundworker MVVM, Update a Progressbar

I have got a StartCommand Class:
public class StartCommand : ICommand
{
public void Execute(object parameter)
{
//Fill Listview1
...
//Here I want to increase the Progressbarvalue
//Fill Listview2
...
//Here again and so far..
}
}
Execute Command will be executed, when clicking the Startbutton on my MainWindow.xaml (where's also the progressBar).
What I want now, is updating the Progressbar on these places (look at Code), while the ListViews are loading. How do I set up the Backgroundworker?
I tried something like that:
public class StartCommand : ICommand
{
MainWindow mainWindow;
public StartCommand(MainWindow mainWindow)
{
this.mainWindow = mainWindow
}
public void Execute(object parameter)
{
//Fill Listview1
...
mainWindow.backgroundWorker1.RunWorkerAsync(10);
//Fill Listview2
...
mainWindow.backgroundWorker1.RunWorkerAsync(20);
}
}
MainWindow:
public partial class MainWindow : Window
{
BackgroundWorker backgroundWorker1;
public MainWindow()
{
InitializeComponent();
InitializeBackgroundWorker();
}
private void InitializeBackgroundWorker()
{
backgroundWorker1.DoWork +=
new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(
backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged +=
new ProgressChangedEventHandler(
backgroundWorker1_ProgressChanged);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
e.Result = UpdateProgressBar((int)e.Argument, worker);
}
private int UpdateProgressBar(int value, BackgroundWorker worker)
{
worker.ReportProgress(value);
return Convert.ToInt32(progressBar.Value);
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}
private void backgroundWorker1_ProgressChanged(object sender,ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
}
}
Thats not working (This is only some Copy/Paste arrangement, cause I have no idea on how to accomplish that, working first time with Threads in WPF). But maybe u got now a better sight, of what I'm looking for..
You have to choose: use one background worker to load all the listviews or use multiple background workers, each filling a listview.
Currently you are trying to force the backgroundworker to start working on another job before the previous job has finished.
To fix this quickly: put all the code to load the listviews in the DoWork handler.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
// fill listview1
worker.ReportProgress(percentageDone);
// fill listview2
worker.ReportProgress(percentageDone);
// fill listview3
worker.ReportProgress(percentageDone);
}
To use multiple Backgroundworkers create multiple Backgroundworkers:
var bw1 = new BackgroundWorker();
bw1.DoWork += ...;
bw1.RunAsync(...);
var bw2 = new BackgroundWorker();
bw2.DoWork += ...;
bw2.RunAsync(...);
var bw3 = new BackgroundWorker();
bw3.DoWork += ...;
bw3.RunAsync(...);
This all depends on what "filling list view" actually means. If you're going off to a database to get data then that definitely should be on a background thread. However, if you already have the data and just need to populate the list view, then a BackgroundWorker won't help at all - it'll make matters worse. That's because you'll need to marshal back to the UI thread in order to access the ListView!
If you have a lot of data to add to a ListView and you want to keep the UI responsive throughout, you need to add the data in chunks in separate dispatcher messages. When a chunk has been added, queue another message to add the next chunk. This gives the dispatcher time to process other messages whilst the list is populating.

Communicate Between two threads

I have sth like that. It's giving me error. I cut out all unneeded parts of code. It is giving me this error
The calling thread cannot access this object because a different thread owns it.
public partial class MainWindow : Window
{
BackgroundWorker worker;
Grafik MainGrafik;
double ProgressBar
{
set { this.progressBarMain.Value = value; }
}
public MainWindow()
{
InitializeComponent();
worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
MainGrafik = new Grafik();
MainGrafik.ProgressUpdate +=
new Grafik.ProgressUpdateDelegate(MainGrafik_ProgressUpdate);
worker.RunWorkerAsync();
}
void MainGrafik_ProgressUpdate(double progress)
{
ProgressBar = progress;
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
while(true)
{
MainGrafik.Refresh();
Thread.Sleep(2000);
}
}
}
class Grafik
{
public delegate void ProgressUpdateDelegate(double progress,
DateTime currTime);
public event ProgressUpdateDelegate ProgressUpdate;
public void Refresh()
{
ProgressUpdate(5); // Just for testing
}
}
You can't update UI objects from another thread. They have to be updated in the UI thread. Try adding this code to the MainGrafik_ProgressUpdate(double progress)
void MainGragfik_ProgressUpdate(double progress)
{
if (InvokeRequired)
{
BeginInvoke((MethodIvoker)(() =>
{
MainGragfik_ProgressUpdate(progress);
}));
return;
}
ProgressBar = progress;
}
The thread firing the ProgressUpdate event is your BackgroundWorker. The ProgressUpdate event handlers are likely running on that thread, and not the UI thread.
in short call this on the form in the context of your other thread's execution:
void MainGrafik_ProgressUpdate(object sender, EventArgs e) {
Action<T> yourAction =>() yourAction;
if(yourForm.InvokeRequired)
yourForm.Invoke(yourAction);
else yourAction;
}
Or with MethodInvoker (blank delegate)
void MainGrafik_ProgressUpdate(object sender, EventArgs e) {
MethodInvoker invoker = delegate(object sender, EventArgs e) {
this.ProgressBar = whatever progress;
};
}

Categories