Issues while using BackgroundWorker for Multithreading in MVP - c#

I have been trying to introduce multithreading in my MVP code to shift the load off on the UI thread so that it is free to perform/display progress notification to the user while the worker thread is executing a long running operation in the background.
Present implementation inside View:
The eventhandler inside class CalculationView (view) invokes a long running operation on CalculationPresenter (presenter)
Something like this:
private void btnCalculate_Click(object sender, EventArgs e)
{
mPresenter.PerformLongRunningTask();
}
Now, To introduce multithreading, I plan to use BackgroundWorker to call the PerformLongRunningTask in the presenter
Initialization code of BackgroundWorker in the Constructor
//Member declaration
BackgroundWorker m_oWorker; //Backgroundworker thread
ProgressDialog dialog; //A simple wait dialog
..
//constructor
..
m_oWorker = new BackgroundWorker();
m_oWorker.DoWork += DoLongRunningWork;
m_oWorker.RunWorkerCompleted += LongRunningWorkCompleted;
Now, I want to call the PerformLongRunningTask method using BackgroundWorker thread, so that I can display a UI form (wait dialog). My BackgroundWorker need not support cancellation or report progress. Hence I'm not setting those properties while initializing BackgroundWorker thread.
private void btnCalculate_Click(object sender, EventArgs e)
{
if(m_oWorker !=null)
{
dialog = new ProgressDialog(); //initialize wait dialog
dialog.Show(); //Display wait dialog
m_oWorker.RunWorkerAsync(); //Invoke BackgroundWorker thread
}
}
//Calls the LongRunning task in the background
void DoLongRunningWork(object sender, DoWorkEventArgs e)
{
mPresenter.PerformLongRunningTask();
}
//Closes the progress dialog displayed on the UI after completion
void LongRunningWorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(dialog != null) dialog.Close()
}
However, with the above code I'm not able to achieve what I'm trying to do. A reference of View class inside the presenter code is throwing cross-thread exception.
"Cross-thread operation not valid: Control 'cmbSelect' accessed from a thread other than the thread it was created on."
Due to which the wait dialog simply appears and closes even before the long running operation has completed its execution.
Any suggestions/pattern to properly access the controls inside the presenter having reference to the view class. (ex: Control.InvokeRequired) in the Presenter code ?

The solution from the comments, to help future readers
The worker considers its work done as soon as the event handler, DoLongRunningWork here, returns. Set a breakpoint in the function and see what happens.
Also see if the Error property of the RunWorkerCompletedEventArgs is set: if so, then the worker thread threw an exception that went unhandled, causing your backgroundworker to return/cancel.

Related

Waiting for Dispatcher.Invoke in WPF UI thread

I've got a BackgroundWorker that occasionally needs to call into the UI thread to perform some work and retrieve a result. To achieve this I'm using the following from within the background thread:
App.Current.Dispatcher.Invoke(new Action(() => { /* some code that updates local data */ });
As the app is exiting, I want to be able to tell the BackgroundWorker to quit but I want to allow it to finish any current operations. In other words, I want to call BackgroundWorkerObj.CancelAsync() and then pump messages until the thread has exited.
I've tried the following, but the Invoke() in the background thread still blocks (though the UI thread is still churning):
Worker.CancelAsync();
while (Worker.IsBusy)
{
DispatcherFrame Frame = new DispatcherFrame();
Frame.Continue = false;
Dispatcher.PushFrame(Frame);
}
What's the correct way to do this? How can the UI thread wait on the BackgroundWorker while still executing Invokes from that BackgroundWorker object?
This sort of shutdown deadlock is exactly why you shouldn't use Invoke for this purpose.
Change it to BeginInvoke(), and for communications back to the worker thread use an event.
I would use Task.Run since you're on .NET 4.0. but anyways. You have to do it the other way around. Wait for the backgroundworker to finish and then exit the application. There is no way to wait for the background thread to finish in an closing event while keeping the main thread responsive. This while loop will block the main thread and message pump until the background thread is done.
Try this
private BackgroundWorker _worker;
protected override OnFormClosing( object sender , FormClosingEventArgs e )
{
base.OnFormClosing( sender , e );
// Cancel's the closing and keeps the form alive
e.Cancel = _worker.IsBusy;
}
private void RunWorkerCompleted( object sender , RunWorkerCompletedEventArgs e)
{
// Work is done, so close the form
Close();
}

Raising Additional Events in BackgroundWorker's DoWork Method

I am trying to run a long running instance method on another thread from my UI, but still report progress back to the GUI thread to update a progress bar. I know there are a lot of ways to do this and many questions on SO regarding this. The long running instance method I have actually has two events of it's own. It has an event that fires on PropertyChanged, which is for reporting of the current work completed & current total work that needs to be completed. It also has an OperationCompleted event. I was planning on using a BackgroundWorker in my UI to run this long-running method on a separate thread. What I am trying to figure out is the following:
If I create event handlers for the PropertyChanged & OperationCompleted events, can I implement them to raise BackgroundWorker events? Will this allow them all run on the background thread and essentially simulate an event bubbling to the UI thread? Below is an example:
class GUI : Form
{
private BackgroundWorker back = new BackgroundWorker();
back.WorkerReportsProgress = true;
back.DoWork += new DoWorkEventHandler(myWork);
back.ProgressChanged += new ProgressChangedEventHandler(myWork_changed);
back.RunWorkerCompleted += RunWorkerCompletedEventHandler(myWork_completed);
/* GUI CODE OMITTED */
private void button_click(object sender, EventArgs e)
{
back.RunWorkerAsync();
}
private void myWork(object sender, DoWorkEventArgs e)
{
Syncer sync = new Syncer();
sync.PropertyChanged += new PropertyChangedEventHandler(sync_PropertyChanged);
sync.OperationCompleted += new EventHandler(sync_OperationCompleted);
sync.LongRunningMethod();
}
private void sync_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//calculations & logic as needed
back.ReportProgress(calculated);
}
//might not be necessary to implement because of how BackgroundWorker functions
private void sync_OperationCompleted(object sender, EventArgs e)
{
back.ReportProgress(100);
}
private void myWork_changed(object sender, ProgressChangedEventArgs e)
{
//update UI progress bar
}
private void myWork_completed(object sender, RunWorkerCompletedEventArgs e)
{
//tasks needed # completion such as hide progress bar
}
So will the events of the Syncer class fire on the same thread as the BackgroundWorker's DoWork() method? Is this thread safe? Criticism & alternative suggestions welcome!
The Syncer() class events will fire from whatever thread is actually raising them. That might be the same thread as the DoWork() handler, it might not be. We don't know the internal workings of the Syncer() class, so we can't tell from the code you've posted.
It's fine, however, to call ReportProgress() from the Syncer() events. Regardless of what thread they fire on, this will still result in the ProgressChanged() and RunWorkerCompleted() events being raised in the main UI thread (assuming the BackgroundWorker() was created by the main UI thread itself; which it was in this case).
You don't need to manually Invoke() anything, that's the whole point of using the BackgroundWorker() in the first place...to avoid the need to do that.
If you didn't use the BackgroundWorker() and "bubble" the events via its built-in events, and instead wrapped the Syncer() operation in a manual thread, then yes, you'd need to manually Invoke() updates to the GUI.
Events raised from the background worker thread will run on and block that thread and can in turn raise ReportProgress events which will also run on and block the worker thread so long as it can access a reference to the BackgroundWorker.
If you bear this in mind and use invoke calls to update the UI it should work fine.

Thread doesn't wake up after join

I've got a GUI interface which has a start and a cancel button. After starting, the main thread which is the GUI thread, is creating a second thread which will do the actual work. When pressing the cancel button, all it does is set a boolean value which tells the working thread to stop its work and end. The problem is that the main GUI thread remain stuck even though I'm sure that the working thread has finished what it was doing. Why is that?
Here is some of the code:
private Thread workerThread;
private SomeClass fs;
private void buttonSearch_Click(object sender, EventArgs e)
{
//do some initializations
fs = new SomeClass();
workerThread = new Thread(fs.WorkMethod);
workerThread.Start();
}
private void buttonCancel_Click(object sender, EventArgs e)
{
fs.StopWork();
workerThread.Join();
}
inside SomeClass:
private bool keepWorking;
public void StopWork()
{
keepWorking= false;
}
public void WorkMethod()
{
if (keepWorking)
{
//do some stuff with recursion
}
}
does someone know why won't the main thread wake up after calling join?
I have also tried debugging to see what happens if I change the keepWorking variable to false manually and the method does reach its' end.
Your WorkMethod has a call to Invoke in there that is invoking a delegate to run on the UI thread and then block until it finishes. Since your UI thread is currently blocking on the call to Join waiting for the background thread, the UI thread is unable to call that delegate.
You now have both threads each waiting on the other, and no progress is being made. This is called a "deadlock".
Also, keepWorking should be marked as volatile as it's being accessed from multiple threads; as it stands the background thread can be accessing an outdated/cached value of that variable for quite some time after the main thread changes it. Marking it as volatile prevents the runtime from making such optimizations.
The solution here is to not block the UI thread with a call to Join. If you need to have some code execute when the background thread ends then you'll need to asynchronously fire that code when the thread finishes instead of synchronously blocking.

BackgroundWorker and Clipboard

I'm going crazy with a simple code in which I use a BackgroundWorker to automate the basic operations. Should I add a content to the clipboard.
After executing this code in the method of the BackgroundWorker:
Clipboard.SetText (splitpermutation [i]);
I get an error that explains the thread must be STA, but I do not understand how to do.
Here more code: (not all)
private readonly BackgroundWorker worker = new BackgroundWorker();
private void btnAvvia_Click(object sender, RoutedEventArgs e)
{
count = lstview.Items.Count;
startY = Convert.ToInt32(txtY.Text);
startX = Convert.ToInt32(txtX.Text);
finalY = Convert.ToInt32(txtFinalPositionY.Text);
finalX = Convert.ToInt32(txtFinalPositionX.Text);
incremento = Convert.ToInt32(txtIncremento.Text);
pausa = Convert.ToInt32(txtPausa.Text);
worker.WorkerSupportsCancellation = true;
worker.RunWorkerAsync();
[...]
}
private void WorkFunction(object sender, DoWorkEventArgs e)
{
[...]
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
else
{
[...]
Clipboard.SetText(splitpermutation[i]);
[...]
}
}
You could marshal this to the UI thread to make it work:
else
{
[...]
this.Dispatcher.BeginInvoke(new Action(() => Clipboard.SetText(splitpermutation[i])));
[...]
}
The BackgroundWorker runs on the .NET thread pool. Thread pool threads run in the COM multi-threaded apartment. To use the clipboard, you must be running in a single-threaded apartment. You could create your own thread and set it to run in an STA, but it would probably be best to use Control.Invoke (or BeginInvoke) to get back onto a user-interface thread (which must be an STA thread).
The exception you're getting is because you're trying to do something on the UI thread from outside the UI thread (a BackgroundWorker, as the name implies, does something in the background, and for that it needs to create a separate thread).
While the answer posted by Reed (that is, by using Dispatcher.BeginInvoke) is one way to do avoid this exception, I'm wondering WHY you are trying to send something to the clipboard from the main work method in the first place...
The BackgroundWorker exposes events like ProgressChanged (which you can call periodically from your work method) or RunWorkerCompleted (which will fire when the main work method finishes).
Using Clipboard.SetText in either of these events should not cause the exception you're seeing, and this seems to be the preferable way of doing things on the UI thread when working with the BackgroundWorker.

Cross thread exception

I am having a problem for a while
this line:
txtPastes.Text = (string)e.UserState;
throws a cross thread exception and I didn't find any solution
txtPastes - textbox
GuiUpdate - BackgroundWorker
lstAllPastes - list of string
private void GuiUpdate_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
while (lstAllPastes.Count == 0) ;
for (int i = 0; i < lstAllPastes[0].Length; i++)
{
GuiUpdate.ReportProgress(0, lstAllPastes[0].Substring(0, i));
Thread.Sleep(1);
}
lstAllPastes.RemoveAt(0);
}
}
private void GuiUpdate_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
txtPastes.Text = (string)e.UserState;
}
private void GuiUpdate_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}
You cannot update a UI control from any thread other than the UI thread. Typically, the BackgroundWorker would take care of raising its ProgressChanged and RunWorkerCompleted events correctly on the UI thread. Since that doesn’t appear to be the case here, you could marshal your UI-updating logic to the UI thread yourself by using the Invoke method:
txtPastes.Invoke(new Action(() =>
{
// This code is executed on the UI thread.
txtPastes.Text = (string)e.UserState;
}));
If you’re on WPF, you would need to call Invoke on the control’s dispatcher:
txtPastes.Dispatcher.Invoke(new Action(() =>
{
txtPastes.Text = (string)e.UserState;
}));
Update: As Thomas Levesque and Hans Passant have mentioned, you should investigate the reason why your ProgressChanged event is not being raised on the UI thread. My suspicion is that you’re starting the BackgroundWorker too early in the application initialization lifecycle, which could lead to race conditions and possibly a NullReferenceException if the first ProgressChanged event is raised before your txtPastes textbox has been initialized.
Well, this is supposed to work of course. The cause of this exception is not visible in your snippet. What matters is exactly where and when the BackgroundWorker is started. Its RunWorkerAsync() method uses the SynchronizationContext.Current property to figure out what thread needs to execute the ProgressChanged event handler.
This can go wrong when:
You started the BGW too early, before the Application.Run() call. Winforms or WPF won't yet have had a chance to install its own synchronization provider.
You called the BGW's RunWorkerAsync() method in a worker thread. Only marshaling to the UI thread is supported, the message loop is the crucial ingredient to make running code on another thread work.
The form that has txtPastes control was created on another thread. With the BGW started on the UI thread that's still a thread mismatch
The form's Show() method was called on another thread. Which creates the native Windows window on the wrong thread.
Make sure you start the BackgroundWorker from the UI thread; if you do that, the ProgressChanged event will be raised on that thread, and the exception won't happen.
If you want to update yout GUI for example TextBox, you should read this article:
Update GUI from another thread

Categories

Resources