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.
Related
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.
Suppose i perform compute-bound task not in GUI thread but in a separate thread. I compute some data that reflects changes the user made in GUI. I need to have computed changes only for the last user action, so i need only one additional thread and cancel it every time user make new change.
How can i say nicely to GUI thread that computations in the worker thread are done? Is there some kind of callback or event, because use timer for the task isn't nice. Thanks.
EDIT: Also i investigated that to return some data from backgroundworker one just need to use Result field of DoWorkEventArgs.
0.Add following using: using System.ComponentModel;
1.Declare background worker:
private readonly BackgroundWorker worker = new BackgroundWorker();
2.Subscribe to events:
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
3.Implement two methods:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
// run all background tasks here
}
private void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
//update ui once worker complete his work
}
4.Run worker async whenever your need.
worker.RunWorkerAsync();
What I want to do:
I'm using a web service DLL in WPF(c#). The DLL contains a web service that you can see it in my code as SmsSender class. Calling each method of this class is time-consuming so I need run its methods in other threads.
What I do:
I set DataView object (that is "returned value" from method) to ItemsSource of DataGrid. So I use Dispatcher.BeginInvoke().
My problem:
my problem is using Dispatcher.BeginInvoke() can freeze my program even I run it in a different thread. I want to call method without freezing. Is it possible to define a time-out?
Update1:
How can I set DataView from time-consuming method to DataGrid?
My code:
Action action = () =>
{
SmsSender sms = new SmsSender();
dgUser1.ItemsSource = sms.GetAllInboxMessagesDataSet().Tables[0].DefaultView;
};
dgUser1.Dispatcher.BeginInvoke(action);
Thanks in advance
Thats easy. You should NEVER do blocking I/O on your UI thread.
SmsSender sms = new SmsSender();
DataView result = sms.GetAllInboxMessagesDataSet().Tables[0].DefaultView
Action action = () =>
{
dgUser1.ItemsSource = result;
};
dgUser1.Dispatcher.BeginInvoke(action);
However this is actually NOT how you should write WPF apps. This pattern is done using WinForms like patterns. You should REALLY be using DataBinding.
Databinding would take care of all your Dispatcher calls.
Dispatcher.BeginInvoke runs delegate on UI thread and since you have put complete action on dispatcher, it will be executed on UI thread which results in UI hang issue.
There are many ways to delegate time consuming operation on to background thread and dispatch UI calls on UI thread.
Simple example is to use BackgroundWorker. Put non-UI stuff in DoWork event handler and UI operation on RunWorkerCompleted handler which is called on UI thread only so need to dispatch calls to UI disptacher.
Small sample -
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork+=new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync();
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
dgUser1.ItemsSource = (DataView)e.Result;
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
SmsSender sms = new SmsSender();
e.Result = sms.GetAllInboxMessagesDataSet().Tables[0].DefaultView;
}
What happens when more than one thread tries to call a form method using Invoke which updates form controls at the same time in Winforms?
static thCount = 0;
private void button1_Click(object sender, EventArgs e)
{
System.Threading.Thread t1 = new System.Threading.Thread(start);
System.Threading.Thread t2 = new System.Threading.Thread(start);
t1.Start();
t2.Start();
}
private void start()
{
System.Threading.Thread.Sleep(300);
Invoke(new MethodInvoker(guiUpdate));
}
private void guiUpdate()
{
this.label1.Text = "Updated.." + (thCount++);
this.label1.Update();
}
private void Form1_Load(object sender, EventArgs e)
{
this.label1.Text = System.Threading.Thread.CurrentThread.Name;
}
Try it out! :) You'll find that neither of them can update the UI from a background thread, instead they need to use Control.BeginInvoke to invoke work on the UI thread, in which case they will execute in the order that they call BeginInvoke.
Either of the thread will not be able to update the GUI.
You might get cross thread exception if you do not check 'InvokeRequired'.
if you still want these threads to access the same method, you can use Mutual Exclusion concept.
You can find more on Mutual Exclusion here.
This question on stack overflow also explain Mutual Exclusion in detail.
Invoke blocks until the thread has finished executing the update method.
However, this is actually only a message to the GUI thread to do this and it waits until it is done. Since the GUI thread can only execute one method at a time there is no real simultaneous execution. Nothing bad happens, but the behaviour may depend on the sequence of execution.
The sequence of execution, however, depends on which thread ever finished some guaranteed atomic (lock) operation.
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