using backgroundworker in Winforms (C#) with MVP pattern - c#

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();
}
}

Related

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.

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.

BackgroundWorker track method from another class

[EDIT]
I edited my question with complete code and explanation and hope something can give me clearer explanation.
I have the following Class that has a backgroundworker to track the percentage progress of a loop and update the percentage progress on a Label on ProgressWin's XAML. The following code works perfectly. (My question is far below, after the code.)
public partial class ProgressWin : Window
{
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
public ProgressWin()
{
InitializeComponent();
InitializeBackgroundWorker();
startAsync();
}
// Set up the BackgroundWorker object by
// attaching event handlers.
private void InitializeBackgroundWorker()
{
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
}
private void startAsync()
{
backgroundWorker1.RunWorkerAsync();
}
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
System.Threading.Thread.Sleep(500);
worker.ReportProgress(i * 10);
}
}
// This event handler updates the progress.
public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Content = (e.ProgressPercentage.ToString() + "%");
}
// This event handler deals with the results of the background operation.
public void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
resultLabel.Content = "Done!";
}
}
Here comes my problem, now instead of tracking the loop within the ProgressWin, I need to track the loop in my MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
for (int i = 1; i <= 10; i++)
{
System.Threading.Thread.Sleep(500);
????.ReportProgress(i * 10);
}
}
}
And I have no idea where to go from here. I tried instantiating an object from ProgressWin and call the DoWork but I end up frozen the ProgressWin window.
Due to the fact that the question was rewritten, i also rewrote my whole answer.
To get this to work your MainWindowhas a ProgressWindow and should use it like a background worker:
public partial class MainWindow : Window
{
private ProgressWindow _Progress;
public MainWindow()
{
InitializeComponent();
_Progress = new ProgressWindow();
_Progress.ProgressChanged += OnProgressChanged;
}
private void OnProgressChanged(object sender, ProjectChangedEventArgs e)
{
//ToDo: Update whatever you like in your MainWindow
}
}
To accomplish this your ProgressWindow should simply subscribe to the worker event and rethrow it:
public partial class ProgressWin : Window
{
// Add this to your class above in your question
public event ProgressChangedEventHandler ProgressChanged;
// Change or merge this with your existing function
private void backgroundWorker1_ProgressChanged(object sender, ProjectChangedEventArgs e)
{
var temp = ProgressChanged;
if(temp !=null)
temp(this, e);
}
}
You can simply call method of another class by doing
backgroudnWorker.DoWork += new DoWorkEventHandler(SomeClass.SomeStaticMethod);
or
backgroudnWorker.DoWork += new DoWorkEventHandler(SomeClassInstance.SomeMethod);
for calling a method of MainWindow class from another class ProgressScreen you should have reference of instance of MainWindow class in ProgressScreen then using that instance you can call any public method of MainWindow from ProgressScreen class
and as Oliver Said, you will need the instance of backgroundworker to send the progress updates from other method.

updating gui from another class c#

hey i am new to c# plz help.
i am writing a program that sorts data in a file and it is a time consuming process so i thought that i should run it in a separate thread and since it has alot of step so i made a new class for it. the problem is that i want to show the progress in the main GUI and i know for that i have to use Invoke function but the problem is that the form control variables are not accessible it this class. what should i do ??????
sample code:
public class Sorter
{
private string _path;
public Sorter(string path)
{
_path = path;
}
public void StartSort()
{
try
{
processFiles(_path, "h4x0r"); // Just kidding
}
catch (Exception e)
{
MessageBox.Show("Error: " + e.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void processFiles(string Dir, string[] key)
{
/* sorting program */
}
and it is used as
public partial class Form1 : Form
{
Sorter sort;
public Form1()
{
InitializeComponent();
}
private void browseBtn_Click(object sender, EventArgs e)
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
textBox1.Text = folderBrowserDialog1.SelectedPath;
}
private void startBtn_Click(object sender, EventArgs e)
{
if (startBtn.Text == "Start Sorting")
{
Thread worker = new Thread(new ThreadStart(delegate() {
sort = new Sorter(textBox1.Text);
sort.StartSort(); }));
worker.start();
}
else
MessageBox.Show("Cancel");//TODO: add cancelling code here
}
}
plz help..
Add an Event to your class that is doing the multi-threaded work, that triggers when the progress changes. Have your form subscribe to this event and update the progress bar.
Note ProgressEventArgs is a little class that inherits EventArgs and has an Integer for the progress.
// delegate to update progress
public delegate void ProgressChangedEventHandler(Object sender, ProgressEventArgs e);
// Event added to your worker class.
public event ProgressChangedEventHandler ProgressUpdateEvent
// Method to raise the event
public void UpdateProgress(object sender, ProgressEventArgs e)
{
ProgressChangedEventHandler handler;
lock (progressUpdateEventLock)
{
handler = progressUpdateEvent;
}
if (handler != null)
handler(sender, e);
}
I would recommend you read up on the BackgroundWorker class. It is exactly for the problem you are trying to solve and makes things a lot easier than doing manual threading yourself.
Brief Example
public Form1()
{
InitializeComponent();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void btnStart_Click(object sender, EventArgs e)
{
if (!backgroundWorker.IsBusy)
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i < 101; ++i)
{
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
break;
}
else
{
//Sort Logic is in here.
Thread.Sleep(250);
backgroundWorker.ReportProgress(i);
}
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy && backgroundWorker.WorkerSupportsCancellation)
backgroundWorker.CancelAsync();
}
You could do something like this:
public delegate void StatusReporter(double progressPercentage);
public class MainClass
{
public void MainMethod()
{
Worker worker = new Worker(ReportProgress);
ThreadStart start = worker.DoWork;
Thread workThread = new Thread(start);
workThread.Start();
}
private void ReportProgress(double progressPercentage)
{
//Report here!!!
}
}
public class Worker
{
private readonly StatusReporter _reportProgress;
public Worker(StatusReporter reportProgress)
{
_reportProgress = reportProgress;
}
public void DoWork()
{
for (int i = 0; i < 100; i++ )
{
// WORK, WORK, WORK
_reportProgress(i);
}
}
}
There are a few option available to solve this sort of issue. In any case, you will have to fiddle with Invoke to get the UI to update.
You could...
...add an event that fires on your new class which your UI can listen to, and Invoke as applicable - you'd still need to pass the data to your worker class (by constructor, properties, method call, etc)
...keep the method as a method on your form, and pas that to start your new thread from (after all, a new thread doesn't have to be starting in a different class)
...change the access modifiers on your controls to be (say) internal such that any class within the same assembly can Invoke changes to the controls, or read from them.
...make your worker class a child of the form it needs to access - it can then see the privates of its parent, as long as it is passed a reference to the instance.

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