"Cross thread operation not valid" Warning - c#

In my c# form application i use BackgroundWorker but couldnt manage to change label1 field. Can anyone help me on that?
Thanks.
private void goButton_Click(object sender, EventArgs e)
{
_worker = new BackgroundWorker();
_worker.WorkerSupportsCancellation = true;
_worker.DoWork += new DoWorkEventHandler((state, args) =>
{
do
{
if (_worker.CancellationPending)
break;
setlabel();
} while (true);
});
_worker.RunWorkerAsync();
goButton.Enabled = false;
stopButton.Enabled = true;
}
private void setlabel()
{
label1.Text = "test";
}

You cannot manipulate controls that were created on the UI thread from a background thread. To accomplish that you can use the BeginInvoke method:
if (_worker.CancellationPending)
break;
this.BeginInvoke(new Action(setlabel));
The method:
Executes the specified delegate asynchronously on the thread that the control's underlying handle was created on.

You can't update UI elements not from UI thread (in your case within DoWork function).
You can use BackgroundWorker's ProgressChangedEventHandler to modify UI state within it's callback.

Related

Disable Winforms button while OnClick code executes in background

I have some code in my "button_click" action. I want to disable the button during this code working.
private void button1_Click(object sender, RoutedEventArgs e)
{
button1.IsEnabled = false;
// some code (its take about 2-5 sec)
button1.IsEnabled = true;
}
But this doesn't work. The button never disables.
You need to run the "some code" part on a background thread:
button1.Enabled = false;
Task.Factory.StartNew(() => {
// some code (its take about 2-5 sec)
}).ContinueWith(task => {
button1.Enabled = true;
}, TaskScheduler.FromCurrentSynchronizationContext());
That is because your UI locks up during the entire action.
You should write the task in some sort of background thread.
You can use the BackgroundWorker for that, but better a Task.
BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += bgw_DoWork;
bgw.RunWorkerCompleted += bgw_RunWorkerCompleted;
button.Enabled = false;
bgw.RunWorkerAsync();
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
// your task
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// update the button
button.Enabled = true;
}
Dispatcher is responsible for message pumping in WPF. Every thread has dispatcher associated with it which is responsible for queuing stuffs on that thread based on DispatcherPriority of items.
In your case GUI rendering is done on DispatcherPriority.Render but right now dispatcher is busy executing your button click event handler code so GUI rendering never happens until it finishes with your code execution. That's why you see button to be refreshed only when your handler gets executed completely.
On a sidenote what McGarnagle proposed gonna work but you can do it other way round as well by explicitly queuing empty delegate on dispatcher with priority Render which will force all queued items with priority higher or equal to Render to be processed before proceeding further. Hence, you will see refresh on your GUI:
button1.IsEnabled = false;
Application.Current.Dispatcher.Invoke((Action)(() => { }),
DispatcherPriority.Render);
// some code (its take about 2-5 sec)
button1.IsEnabled = true;

Cross thread operation not valid when use backgroundworker

Below is my coding:
Form2 msgForm;
private void button3_Click_1(object sender, EventArgs e)
{
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
//bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
msgForm = new Form2();
try
{
bw.RunWorkerAsync();
msgForm.ShowDialog();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
if (comboBox15.Text == "")
{
//MessageBox.Show("Please select Printer ID.", "Status", MessageBoxButtons.OK, MessageBoxIcon.Error);
//return;
}
// Coding that transmit protocol and will last around 2 minutes.
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
msgForm.Close();
}
When I run this background worker coding, there's an error stating "Cross-thread operation not valid: Control 'comboBox15' accessed from a thread other than the thread it was created on."
How do I solve this problem guys?
You can use Invoke:
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.comboBox15.InvokeRequired)
{
this.Invoke((MethodInvoker) delegate {if (comboBox15.Text == ""){// What you want to do}});
}
else
{
if (comboBox15.Text == "")
{
}
}
also read the following:
http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx
Anonymous method in Invoke call
You can't UI elements from a non-UI-thread. Ideally, provide the relevant information to the background worker before it starts, e.g.
string text = combo15.Text;
bw.DoWork += (sender, args) => TransmitStuff(combo15.Text, args);
...
void TransmitStuff(string printerId, DoWorkEventArgs e)
{
...
}
If you can use .NET 4.5 and C# 5, you could use an async method to quite possibly make all of this easier... but I realize that's unlikely to be an option for you.
EDIT: While you can use Invoke, that ends up being quite messy - and you've got potentially inconsistent state. I generally think it's tidier to work out all the state you need before you start the long-running operation, validate it all, and then hand it to the operation. If you need to update the UI during the operation, you can use the BackgroundWorker progress facilities.
In BackgroundWorker, when we call any user controls its problem. Please use this property in Window Form Load event:
CheckForIllegalCrossThreadCalls = false;
You can only access gui controls from your main thread.
Move the
if (comboBox15.Text == "")
part to button3_click
You can get round it by passing the value such as below.
private void Dowork()
{
backgroundWorker1.RunWorkerAsync(comboBox1.Text);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
String selected = (String)e.Argument;
if (String.IsNullOrEmpty(selected)) return;
//do stuff
}

Backgroundworker not cancelling

So I have a webpage scraper that uses backgroundworker to process each page. I also want to mention that I'm using MVVM light framework.
Inside my MainViewModel Constructor I am initializing the backgroundworker:
backgroundWorker = new BackgroundWorker()
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
On the LoadCompleted event of a WebBrowser control I start the backgroundworker:
wb = sender; //sender is the webbrowser control
if (!backgroundWorker.IsBusy)
{
backgroundWorker.RunWorkerAsync();
}
My next two methods are DoWork And StopWork:
private System.Threading.AutoResetEvent _resetEvent = new System.Threading.AutoResetEvent(false);
private object wb;
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker wk = sender as BackgroundWorker;
if (wb != null)
{
FetchPage(wb);
if (wk.CancellationPending)
{
MessageBox.Show("Cancellation pending!");
}
_resetEvent.Set();
}
}
private void StopWork(object sender)
{
backgroundWorker.CancelAsync();
_resetEvent.WaitOne();
}
The fetchpage method will grab the sourcecode of the webbrowser control and start parsing it for content.
Inside of FetchPage I'm using BeginInvoke to update my UI thread:
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(
() =>
{ ... }
My issue:
When I hit the Cancel button the StopWork method get's invoked, the cancel property on the backgroundWorker is set correctly to true, but the app just keeps going on. My if (wk.CancellationPending) is always false.
Any idea on what am I doing wrong here? I looked at tons of examples online and here on StackOverflow and they all state the same things that i already done.
Thanks.
EDIT:
After Ernos reply I tried passing the CancellationPending property to the FetchPage method and check for it in different locations, but it did not stop the processing.
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker wk = sender as BackgroundWorker;
if (wb != null)
{
FetchPage(wb, wk.CancellationPending);
_resetEvent.Set();
}
}
Inside of FetchPage I'm using BeginInvoke to update my UI thread:
private void FetchPage(object sender, bool stopAll)
{
if (stopAll)
{
return;
}
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(
() =>
{ ... }
What I have tried and worked was:
private bool stopAllWork = false;
...
private void StopWork(object sender)
{
stopAllWork = true;
backgroundWorker.CancelAsync();
_resetEvent.WaitOne();
}
and then inside DoWork:
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker wk = sender as BackgroundWorker;
if (wb != null)
{
FetchPage(wb, stopAllWork);
_resetEvent.Set();
}
}
Now, because of this implementation my concern is if there will be any rogue backgroundWorkers remaining?
You need to evaluate the CancellationPending inside the FetchPage method.
You are checking it AFTER the load of work.
Erno is correct. You are checking if it is cancelled after you have done all of the work. To keep things modular, you may consider not passing the background worker to FetchPage; rather pass a function that returns if you should cancel.
public void FetchPage(WebBrowser wb, Func<bool> cancelNow)
{
...
if(cancelNow()) {
return;
}
...
}
You would call it like so
FetchPage(wb, () => wk.CancellationPending);
but you could put that function in another application that does not use a background worker and call it like so:
FetchPage(wb, () => false);
Note: make sure you are checking if you should cancel as the work is being completed. For example if most of the work happens in a loop, check inside the loop. If there are a series of steps, check between each step.

BackgroundWorker returns to wrong thread

In my application, I create a new UI-Thread with the fallowing code:
Thread thread = new Thread(() =>
{
MyWindow windowInAnotherThread = new MyWindow();
windowInAnotherThread.Show();
System.Windows.Threading.Dispatcher.Run();
}) { IsBackground = true };
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
This give me the fallowing problem:
In the constructor of the MyWindow class, a BackgroundWorker is executed. In the RunWorkerCompleted there should a Control be updated with some data, which the BackgroundWorker is calculating.
I have build a small sample, which is illustrating this:
public partial class MyWindow : Window {
public MyWindow() {
InitializeComponent();
var bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.RunWorkerAsync();
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
this.Title = "Calculated title";
}
void bw_DoWork(object sender, DoWorkEventArgs e) {
Thread.Sleep(3000);
}
}
In bw_RunWorkerCompleted() I get an InvalidOperationException (The calling thread cannot access this object because a different thread owns it.). It looks like, that the BackgroundWorker is not returning to the correct UI-Thread from which it was started from.
Can someone help me, what I can do to solve this problem? I can't change the Code which is executing the BackgroundWorker, because it is in a framework, which I use. But I can do something else in the RunWorkerCompleted-Event. But I have no idea, how to solve this problem.
The problem is that the window is getting created too soon. The thread doesn't have a synchronization context yet. You can see this is the debugger by setting a breakpoint on BGW constructor call and look at Thread.CurrentThread.ExecutionContext.SynchronizationContext. It's null. Which is what BGW uses to decide how to marshal the RunWorkerCompleted event. Which no synchronization context, the event runs on a threadpool thread and that invokes wrath.
You need to get the dispatcher initialized sooner. Not 100% this is the correct way but it did seem to work:
Thread thread = new Thread(() => {
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => {
MyWindow windowInAnotherThread = new MyWindow();
windowInAnotherThread.Show();
}));
System.Windows.Threading.Dispatcher.Run();
}) { IsBackground = true };
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
You also have to explicitly force the thread to shutdown. Add this method to MyWindow:
protected override void OnClosed(EventArgs e) {
Dispatcher.BeginInvokeShutdown(System.Windows.Threading.DispatcherPriority.Background);
}
Ran into similar issue. Based on note 1 and 2 below I created UIBackgroundWorker. May be it can help other developers who encounter this issue.
If it works then please let me know or update the design for benefit of other developers.
public class UIBackgroundWorker : BackgroundWorker
{
private System.Windows.Threading.Dispatcher uiDispatcher;
public SafeUIBackgroundWorker(System.Windows.Threading.Dispatcher uiDispatcher)
: base()
{
if (uiDispatcher == null)
throw new Exception("System.Windows.Threading.Dispatcher instance required while creating UIBackgroundWorker");
else
this.uiDispatcher = uiDispatcher;
}
protected override void OnProgressChanged(ProgressChangedEventArgs e)
{
if (uiDispatcher.CheckAccess())
base.OnProgressChanged(e);
else
uiDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => base.OnProgressChanged(e)));
}
protected override void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e)
{
if (uiDispatcher.CheckAccess())
base.OnRunWorkerCompleted(e);
else
uiDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => base.OnRunWorkerCompleted(e)));
}
}
The problem is that you need to setup the SynchronizationContext. This is normally not an issue, as Dispatcher.Invoke will set it up for you, but since you're using BackgroundWorker in the constructor (which is fired prior to Dispatcher.Run), no context is setup.
Change your thread creation to:
Thread thread = new Thread(() =>
{
// Create the current dispatcher (done via CurrentDispatcher)
var dispatcher = Dispatcher.CurrentDispatcher;
// Set the context
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(dispatcher));
MyWindow windowInAnotherThread = new MyWindow();
windowInAnotherThread.Show();
Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
This will cause it to run correctly, as the SynchronizationContext will be in place prior to the construction of the Window.
Try providing a getter and setter for your BackgroundWorker inside MyWindow. And pass BackgroundWorker object via setter method to Mywindow. That should solve the problem, i guess.
You need to use a delegate method and an invoke in the calling function. There's a good example here: http://msdn.microsoft.com/en-us/library/aa288459(v=vs.71).aspx
Using your code,
public partial class MyWindow : Window {
delegate void TitleSetter(string title);
public MyWindow() {
InitializeComponent();
var bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.RunWorkerAsync();
}
void SetTitle(string T)
{
this.Title = T;
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
try
{
TitleSetter T = new TitleSetter(SetTitle);
invoke(T, new object[]{"Whatever the title should be"}); //This can fail horribly, need the try/catch logic.
}catch (Exception){}
}
void bw_DoWork(object sender, DoWorkEventArgs e) {
Thread.Sleep(3000);
}
}
I think simply moving your background worker thread setup code into the "Load" event instead of the constructor should be just fine.

Why does setting a form's enabled property crash the application?

private void launchbutton_Click(object sender, EventArgs e)
{
launchbutton.Enabled = false;
Process proc = new Process();
proc.EnableRaisingEvents = true;
proc.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
//The arguments/filename is set here, just removed for privacy.
proc.Exited += new EventHandler(procExit);
proc.Start();
}
private void procExit(object sender, EventArgs e)
{
MessageBox.Show("YAY","WOOT");
Thread.Sleep(2000);
launchbutton.Enabled = true;
}
2 Seconds after I quit the created process, my program crashes. Why?
You're modifying a winform control on a different thread than the one that created that control (the main UI thread). Winform controls are not thread-safe and typically will throw an exception if you modify their state from any thread other than the one that created it.
You can accomplish this using the InvokeRequired property and BeginInvoke method found on the Form or control object.
For example, something like this:
private void procExit(object sender, EventArgs e)
{
MessageBox.Show("YAY", "WOOT");
Thread.Sleep(2000);
// ProcessStatus is just a class I made up to demonstrate passing data back to the UI
processComplete(new ProcessStatus { Success = true });
}
private void processComplete(ProcessStatus status)
{
if (this.InvokeRequired)
{
// We are in the wrong thread! We need to use BeginInvoke in order to execute on the correct thread.
// create a delegate pointing back to this same function, passing in the same data
this.BeginInvoke(new Action<ProcessStatus>(this.processComplete), status);
}
else
{
// check status info
if (status.Success)
{
// handle success, if applicable
}
else
{
// handle failure, if applicable
}
// this line of code is now safe to execute, because the BeginInvoke method ensured that the correct thread was used to execute this code.
launchbutton.Enabled = true;
}
}

Categories

Resources