I've been struggling with this for quite a while:
I have a function designed to add control to a panel with cross-thread handling, the problem is that though the panel and the control are in "InvokeRequired=false" - I get an exception telling me that one of the controls inner controls are accessed from a thread other than the thread it was created on, the snippet goes like this:
public delegate void AddControlToPanelDlgt(Panel panel, Control ctrl);
public void AddControlToPanel(Panel panel, Control ctrl)
{
if (panel.InvokeRequired)
{
panel.Invoke(new AddControlToPanelDlgt(AddControlToPanel),panel,ctrl);
return;
}
if (ctrl.InvokeRequired)
{
ctrl.Invoke(new AddControlToPanelDlgt(AddControlToPanel),panel,ctrl);
return;
}
panel.Controls.Add(ctrl); //<-- here is where the exception is raised
}
the exception message goes like this:
"Cross-thread operation not valid: Control 'pnlFoo' accessed from a thread other than the thread it was created on"
('pnlFoo' is under ctrl.Controls)
How can I add ctrl to panel?!
When the code reaches the "panel.Controls.Add(ctrl);" line - both panel and ctrl "InvokeRequired" property is set to false, the problem is the the controls inside ctrl has "InvokeRequired" set to true. To clarify things: "panel" is created on the base thread and "ctrl" on the new thread, therefore, "panel" has to be invoked (causing "ctrl" to need invoke again). Once both of the invokes are done, it reaches the panel.Controls.Add(ctrl) command (both "panel" and "ctrl" doesn't need invocation in this state)
Here is a small snippet of the full program:
public class ucFoo : UserControl
{
private Panel pnlFoo = new Panel();
public ucFoo()
{
this.Controls.Add(pnlFoo);
}
}
public class ucFoo2 : UserControl
{
private Panel pnlFooContainer = new Panel();
public ucFoo2()
{
this.Controls.Add(pnlFooContainer);
Thread t = new Thread(new ThreadStart(AddFooControlToFooConatiner());
t.Start()
}
private AddFooControlToFooConatiner()
{
ucFoo foo = new ucFoo();
this.pnlFooContainer.Controls.Add(ucFoo); //<-- this is where the exception is raised
}
}
As an aside - to save yourself having to create countless delegate types:
if (panel.InvokeRequired)
{
panel.Invoke((MethodInvoker) delegate { AddControlToPanel(panel,ctrl); } );
return;
}
Additionally, this now does regular static checks on the inner call to AddControlToPanel, so you can't get it wrong.
'panel' and 'ctrl' must be created on the same thread, ie. you cannot have panel.InvokeRequired return different value than ctrl.InvokeRequired. That is if both panel and ctrl have the handles created or belong to a container with the handle created. From MSDN:
If the control's handle does not yet
exist, InvokeRequired searches up the
control's parent chain until it finds
a control or form that does have a
window handle. If no appropriate
handle can be found, the
InvokeRequired method returns false.
As it is right now your code is open to race conditions because the panel.InvokeNeeded can return false because the panel is not yet created, then ctrl.InvokeNeeded will certainly return false because most likely ctrl is not yet added to any container and then by the time you reach panel.Controls.Add the panel was created in the main thread, so the call will fail.
Where is pnlFoo being created, and in which thread? Do you know when its handle is being created? If it's being created in the original (non-UI) thread, that's the problem.
All control handles in the same window should be created and accessed on the same thread. At that point, you shouldn't need two checks for whether Invoke is required, because ctrl and panel should be using the same thread.
If this doesn't help, please provide a short but complete program to demonstrate the problem.
Here is a working piece of code :
public delegate void AddControlToPanelDlg(Panel p, Control c);
private void AddControlToPanel(Panel p, Control c)
{
p.Controls.Add(c);
}
private void AddNewContol(object state)
{
object[] param = (object[])state;
Panel p = (Panel)param[0];
Control c = (Control)param[1]
if (p.InvokeRequired)
{
p.Invoke(new AddControlToPanelDlg(AddControlToPanel), p, c);
}
else
{
AddControlToPanel(p, c);
}
}
And here is how I tested it. You need to have a form with 2 buttons and one flowLayoutPanel (I chose this so I didn't have to care about location hwhen dinamically adding controls in the panel)
private void button1_Click(object sender, EventArgs e)
{
AddNewContol(new object[]{flowLayoutPanel1, CreateButton(DateTime.Now.Ticks.ToString())});
}
private void button2_Click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(AddNewContol), new object[] { flowLayoutPanel1, CreateButton(DateTime.Now.Ticks.ToString()) });
}
I that he probem with your exaple is that when you get in the InvokeRequired branch you invoke the same function wou are in, resulting in a strange case of recurssion.
In your own answer you state:
To clarify things: "panel" is created on the base thread and "ctrl" on the new thread
I think this might be the cause of your problem. All UI elements should be created on the same thread (the base one). If you need to create "ctrl" as a consequence of some action in the new thread, then fire an event back to the base thread and do the creation there.
Lots of interesting answers here, but one key item for any multithreading in a Winform app is using the BackgroundWorker to initiate threads, and communicate back to the main Winform thread.
Here is a small snippet of the full program:
public class ucFoo : UserControl
{
private Panel pnlFoo = new Panel();
public ucFoo()
{
this.Controls.Add(pnlFoo);
}
}
public class ucFoo2 : UserControl
{
private Panel pnlFooContainer = new Panel();
public ucFoo2()
{
this.Controls.Add(pnlFooContainer);
Thread t = new Thread(new ThreadStart(AddFooControlToFooConatiner());
t.Start()
}
private AddFooControlToFooConatiner()
{
ucFoo foo = new ucFoo();
this.pnlFooContainer.Controls.Add(ucFoo); //<-- this is where the exception is raised
}
}
Related
I have a issue with thread, I've searched for a few days but still cannot solve it..
Due to some reason, I customize a progress form and use it in threads.
I tried to write all functions inside the progress form so that they are wrapped by Invoke and delegate. Unfortunately, this code is not working properly since this.InvokeRequired is returning false when I expected it to return true.
The problem is, when I execute the program, sometimes it throw an exception:
Cross-thread operation not valid: Control 'FormProgress' accessed from a thread other than the thread it was create on.
Here's the code of progress form.
I've wrapped all functions with Invoke and delegate.
public partial class FormProgress : Form
{
public FormProgress()
{
InitializeComponent();
}
public void SetStatusLabelText(string text)
{
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker) delegate
{
label1.Text = text;
});
}
else
{
// exception thrown here
label1.Text = text;
}
}
public void SetDialogResult(DialogResult dialogResult)
{
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
if (DialogResult == DialogResult.None)
this.DialogResult = dialogResult;
});
}
else
{
if (DialogResult == DialogResult.None)
this.DialogResult = dialogResult;
}
}
}
Here's the code of thread, the exception throws when I click button1
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
for (int i=0; i<100; i++)
ProgressTest();
}
private void ProgressTest()
{
FormProgress dialog = new FormProgress();
{
Thread threadTest = new Thread(delegate ()
{
dialog.SetStatusLabelText("initial....(1)");
Thread.Sleep(50);
dialog.SetStatusLabelText("initial....(2)");
Thread.Sleep(50);
dialog.SetStatusLabelText("initial....(3)");
Thread.Sleep(50);
dialog.SetDialogResult(DialogResult.OK);
});
threadTest.Name = "ThreadTest";
threadTest.Start();
if (dialog.ShowDialog() == DialogResult.Cancel)
{
if (threadTest.IsAlive)
threadTest.Abort();
}
threadTest.Join();
}
}
}
As per the docs:
If the control's handle does not yet exist, InvokeRequired searches up
the control's parent chain until it finds a control or form that does
have a window handle. If no appropriate handle can be found, the
InvokeRequired method returns false.
This means that InvokeRequired can return false if Invoke is not
required (the call occurs on the same thread), or if the control was
created on a different thread but the control's handle has not yet
been created.
In the case where the control's handle has not yet been created, you
should not simply call properties, methods, or events on the control.
This might cause the control's handle to be created on the background
thread, isolating the control on a thread without a message pump and
making the application unstable.
You can protect against this case by also checking the value of
IsHandleCreated when InvokeRequired returns false on a background
thread. If the control handle has not yet been created, you must wait
until it has been created before calling Invoke or BeginInvoke.
Typically, this happens only if a background thread is created in the
constructor of the primary form for the application (as in
Application.Run(new MainForm()), before the form has been shown or
Application.Run has been called.
The issue you have is that some of your InvokeRequired calls may be occurring before the form has been shown. This is because you are starting your new thread before calling dialog.ShowDialog(). Note, as is common with race conditions, the problem won't always occur - just sometimes.
As per above, you may want to consider checking IsHandleCreated before executing the logic in your else blocks (after checking InvokeRequired) to protect against this possibility. Alternatively, rethink your entire strategy around the progress form.
do changes with controls inside if (this.InvokeRequired) block
remove the else block staying after if (this.InvokeRequired)
public void SetStatusLabelText(string text)
{
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker) delegate
{
label1.Text = text;
});
}
}
public void SetDialogResult(DialogResult dialogResult)
{
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
if (DialogResult == DialogResult.None)
this.DialogResult = dialogResult;
});
}
}
let's consider method ProgressTest(), what happaning:
after threadTest.Start() has been called , the threadTest method starts execution of his work item in a new thread
after dialog.ShowDialog() the GUI thread become blocked , it makes this.InvokeRequired = false
at the same time threadTest keep working and when threadTest try to execute
else
{
label1.Text = text;
}
label1.Text setter is called from NONE GUI thread (it is called from "ThreadTest" thread), that's why you get exception
It should be noted that dialog.SetStatusLabelText("initial....") which supposed to be called 300 times , actually will be called less then 300
I am facing to a very strange issue when I use dispatcher.invoke method in wpf.
Background:
I defined a user control there is a DoWorkEventArgs to support some async work:
public class MyUserControl : UserControl
{
private BackgroundWorker bw;
public MyUserControl()
{
bw.DoWork += new DoWorkEventHandler(DoWorkMethod);
}
public void StartWork()
{
bw.RunWorkerAsync();
}
void DoWorkMethod(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke((System.Action)delegate()
{
//Add some item in a ListBox, this ListBox is defined in the user control.
TextBlock b = new TextBlock();
//some code
Listbox.Items.Add(b);
}
}
}
When a button click I created 2 instance of this user control and call there StartWork method:
MyUserControl control1 = new MyUserControl();
MyUserControl control2 = new MyUserControl();
control1.StartWork();
control2.StartWork();
Here is the problem, sometimes the ListBox in usercontrol1 is not updated, there is no item in it, sometimes this situation happend in the ListBox of usercontrol2, I debug them and I found the code runs normal, the ListBox.Items.Add method runs, and the results just don't come out.
If I change Dispatcher.Invoie to Dispatcher.BeginInvoke, then it's normal.
Is anyone know the reason?
Firstly, your use of the BackgroundWorker is invalid because you are trying to run the DowWork method on the UI thread, but the whole purpose of that method is that it runs on a background thread. If you want to know how to correctly implement a BackgroundWorker, then please see my answer to the How to correctly implement a BackgroundWorker with ProgressBar updates? question here on Stack Overflow.
However, if you just want to run some code asynchronously, you really don't need to use a BackgroundWorker these days. Instead, you can use the Task class to do something like this:
Task.Factory.StartNew(() => NameOfMethodToRunAsynchronously);
If you need to run part of the NameOfMethodToRunAsynchronously method on the UI thread, then you can return to your Dispatcher.Invoke call:
private void NameOfMethodToRunAsynchronously()
{
// Some long running process
Dispatcher.Invoke((System.Action)delegate()
{
//Add some item in a ListBox, this ListBox is defined in the user control.
TextBlock b = new TextBlock();
//some code
Listbox.Items.Add(b);
}
}
Finally, to answer your original concern about the Dispatcher class, please see the Dispatcher Class page on MSDN. From that page:
Provides services for managing the queue of work items for a thread.
Note that it says a thread and not the UI thread. This means that it could be the UI thread like you want, but not necessarily. In order to ensure that it will be for the UI thread, we can simply set it on the UI thread in the constructor of MainWindow.xaml.cs:
Dispatcher uiDispatcher = Application.CurrentDispatcher;
So to ensure that your Dispatcher will run on the UI thread, just use that instance:
uiDispatcher.Invoke((System.Action)delegate()
{
//Add some item in a ListBox, this ListBox is defined in the user control.
TextBlock b = new TextBlock();
//some code
Listbox.Items.Add(b);
}
I have a Windows Form, inside I have a Button and a Panel.
On button click I'm adding a control to the panel... as many as I want. This process is using Task Factory. Example:
private void ButtonClick()
{
// This line needs to happen on the UI thread...
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
CustomControl.PersonResult newControl = new CustomControl.PersonResult();
this.panel1.Controls.Add(newControl);
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
}
My PersonResult.cs control has the following layout:
The control has a picturebox and 3 lables.
Now, when a new PersonResult control is added to the form, I want to perform other background worker in order to get an image from the internet and place it in the picturebox.
So, the scenario is:
I press the button many times and immediately I will see the customcontrol added to the panel, but the picturebox of every control will be empty, but then images will start appearing as soon as the worker loads the image from internet and place it on the picturebox.
Any clue on how do implement this?
Thanks a lot
Any time you touch a control you must do so from the UI thread. You can do the actual downloading of the picture and other long-running tasks from your thread/task. From your thread you use Control.Invoke to invoke a delegate on the UI thread.
This MSDN article has a good example of using Control.Invoke with simple variables like strings and such:
http://msdn.microsoft.com/en-us/library/a1hetckb(v=vs.100).aspx
But often a helper class is used so you can pass more complex data between your delegates without resorting to big nasty object arrays.
Here's some sample code I did up:
private void button1_Click(object sender, EventArgs e) {
Task.Factory.StartNew(() =>
{
DataClass dataClass = new DataClass(this.textBox1);
Thread.Sleep(5000); // simulate long running task
dataClass.TextToPass = "set some text";
dataClass.updateTargetControl();
});
}
private class DataClass {
delegate void updateTextBoxOnUiThreadDelegate();
private TextBox _targetControl= null;
private updateTextBoxOnUiThreadDelegate _updateDelegate;
internal DataClass(TextBox targetControl) {
_updateDelegate = new updateTextBoxOnUiThreadDelegate(updateOnUiThread);
_targetControl = targetControl;
}
internal string TextToPass = "";
internal void updateTargetControl() {
_targetControl.Invoke(_updateDelegate);
}
private void updateOnUiThread() {
_targetControl.Text = this.TextToPass;
}
}
Something along the lines:
You don't have to add controls asynchronously. Add them in the GUI thread and every time create a new worker thread supplying it with the delegate from your control which will be called asynchronously (using BeginInvoke?) when the worker finished loading the image.
I am not quite sure I understand why you've wrapped a UI operation in its own Task.. when it isn't chained to an async Task.
Anyway.. PictureBoxes have a LoadAsync method. Why make this harder than it needs to be?
private void ButtonClick()
{
CustomControl.PersonResult newControl = new CustomControl.PersonResult();
this.panel1.Controls.Add(newControl);
newControl.PictureBox.WaitOnLoad = false;
newControl.PictureBox.LoadAsync("http://url-here.com/image.jpg");
}
I need to use threading in my app, but I don't know how to perform a cross threading operation.
I want to be able to change the text of a form object (in this case a Combo Box), from another thread, I get the error:
Cross-thread operation not valid: Control 'titlescomboBox' accessed from a thread other than the thread it was created on.
I don't really understand how to use the invoke and begin invoke functions, So im really looking for a dead simple example and explanation for this so I can learn around that.
Also any beginner tutorials would be great, I found a few, but their all so different, I don't understand exactly what I need to do to perform cross threading ops.
Here is the code:
// Main Thread. On click of the refresh button
private void refreshButton_Click(object sender, EventArgs e)
{
titlescomboBox.Items.Clear();
Thread t1 = new Thread(updateCombo);
t1.Start();
}
// This function updates the combo box with the rssData
private void updateCombo()
{
rssData = getRssData(channelTextBox.Text); // Getting the Data
for (int i = 0; i < rssData.GetLength(0); i++) // Output it
{
if (rssData[i, 0] != null)
{
// Cross-thread operation not valid: Control 'titlescomboBox'
// accessed from a thread other than the thread it was created on.
titlescomboBox.Items.Add(rssData[i, 0]); // Here I get an Error
}
titlescomboBox.SelectedIndex = 0;
}
}
I use the following helper class:
public static class ControlExtensions
{
public static void Invoke(this Control control, Action action)
{
if (control.InvokeRequired)
{
control.Invoke(new MethodInvoker(action), null);
}
else
{
action.Invoke();
}
}
}
Now you can call something like MyCombo.Invoke(() => { MyCombo.Items.Add(something); }) --- or any other control (such as the form) before the invoke since they are all created on the main thread.
The thing is that controls can only be accessed from the thread they were created on (the main application thread in this case).
HTH
This exception is thrown because of you are trying to access to a control members that is created on another thread. When using controls you should access the control members only from the thread that the control created on.
The control class helps you to know weather the control is no on the thread that is created on or not by providing InvokeRequeired property. so if 'control.InvokeRequeired' returns true that indicates that you are on a different thread. to help you out. Control support Invoke and BeginInvoke methods that will handle the execution of method to the control main thread. So:
If you are using 3.5 and above, I suggest you to use the extension method that Eben Roux show in his answer.
For 2.0:
// This function updates the combo box with the rssData
private void updateCombo()
{
MethodInvoker method = new MethodInvoker(delegate()
{
rssData = getRssData(channelTextBox.Text); // Getting the Data
for (int i = 0; i < rssData.GetLength(0); i++) // Output it
{
if (rssData[i, 0] != null)
{
// Cross-thread operation not valid: Control 'titlescomboBox'
// accessed from a thread other than the thread it was created on.
titlescomboBox.Items.Add(rssData[i, 0]); // Here I get an Error
}
titlescomboBox.SelectedIndex = 0;
}
});
if (titlescomboBox.InvokeRequired)//if true then we are not on the control thread
{
titlescomboBox.Invoke(method);//use invoke to handle execution of this delegate in main thread
}
else
{
method();//execute the operation directly because we are on the control thread.
}
}
if you use C# 2.0 this
Take a look at this What is the best way to update form controls from a worker thread? - it should resolve your issue.
I have a problem with this code.
It's generates this exception:
Text' threw an exception of type
'Microsoft.VisualStudio.Debugger.Runtime.CrossThreadMessagingException
private void Form1_Load(object sender, EventArgs e)
{
Timer = new System.Threading.Timer(
TimerTick, null, TimeSpan.Zero, new TimeSpan(0, refresh , 0));
}
void TimerTick(object state)
{
LoggerTxt.AppendText("fsjdaò");
}
LoggerTxt is a TextBox.
How I can do?
thanks
You can access GUI components in a Windows Forms application only from within the foreground thread. (I think, this is also true for WPF applications)
Since you are trying to call a function on the TextBox (a GUI component) from the timer function (in the background thread) you get the exception.
Try
LoggerTxt.Invoke(
new MethodInvoker(
delegate { LoggerTxt.AppendText("fsjdaò"); } ) );
To avoid the exception.
Also see the documentation of Control.Invoke for more on this topic and this similar SO posting.
As Uwe has commented you cannot access or modify a GUI component not on the GUI thread therefore you have to usually invoke this.
If you are going to do this a lot why not add this class to your projects so that all control objects have this method exposed to them.
You can use LoggerTxt.RunInGUIThread(x => x.AppendText("fsjdao"));
public static class ControlExtensions
{
public static void RunInGUIThread<TControl>(this TControl control, Action<TControl> action)
where TControl: Control
{
if (control.InvokeRequired)
control.Invoke(action, control);
else
action(control);
}
}