Making a Controls.Add method call thread-safe - c#

Let's say I have the following code:
public void Inject(Form subform)
{
this.tabControl1.TabPages[1].Controls.Add(subform);
this.Refresh();
}
How can I convert the Controls.Add() call to a thread-safe call, using Control.Invoke?

The only way to make Control.Add thread safe is to make sure it's called from the UI thread. This also implies that the Control being added is usable from the UI thread.
Here is a function though which produces a delegate which can add to a Control from any thread (presuming the added Control is OK on the UI thread).
public Action<Control> GetAddControl(this Control c)
{
var context = SynchronizationContext.Current;
return (control) =>
{
context.Send(_ => c.Controls.Add(control), null);
};
}
Then for a given Control you can pass the resulting delegate to any thread.
// From UI thread
Action<Control> addControl = c.GetAddControl();
// From background thread
addControl(subForm);

Related

"The calling thread must be STA, because many UI components require this" while creating/adding new wpf usercontrol

I have a usertaskpane in VSTO add-in. I'm adding there winformshost and elementhost to be able to use wpf controls inside usertaskpane.
I managed to add a main wpf control, but I am failing with adding child user control to that.
I have such method that initiates adding new wpf control:
private void MasterCheck()
{
this.pnlProgress.Visibility = System.Windows.Visibility.Visible;
//I'm using progress bar functionality in ReturnMasters method
Thread myNewThread = new Thread(() => Auditor.AuditMasterSlides(Globals.ThisAddIn.Application.ActivePresentation, this.pnlMaster, this, token));
token = new CancellationTokenSource();
myNewThread.Start();
this.pnlProgress.Visibility = System.Windows.Visibility.Collapsed;
}
public static void AuditMasterSlides(PPT.Presentation pres, Panel panel, MainProofingTaskPaneControl control, CancellationTokenSource cancToken)
{
IDictionary<string,MasterSlide> masterSlides = ReturnMasters(pres, cancToken, control);
control.ShowAndCollapse(panel);
control.RemovePanelChildren(panel);
if (masterSlides.Count>1)
{
//control.AddControlToPanel(panel, new MasterCheckControlOK());
}
else
{
control.AddControlToPanel(panel, new MasterCheckControlOK());
}
}
internal void RemovePanelChildren(Panel panel)
{
this.Dispatcher.Invoke(() =>
{
for (int i = panel.Children.Count - 1; i >= 0; i--)
{
panel.Children.RemoveAt(i);
}
});
}
internal void AddControlToPanel(Panel panel, Control control)
{
MasterCheckControlOK newControl = new MasterCheckControlOK();
this.Dispatcher.Invoke(() =>
{
panel.Children.Add(newControl);
});
}
And I'm getting error here:
public MasterCheckControlOK()
{
InitializeComponent();
}
How can I solve it to be able to:
use progress bar functionality (currently works)
add new wpf controls (does not work)
modify/remove controls (currently works)
You can only create UI controls on STA (single-threaded apartment) threads:
The calling thread must be STA, because many UI components require this
You can only access a control on the thread on which it was originally created. For example, you cannot create a control on a thread B and then try to add it to the Children collection of a control that was created on thread A.
So it makes no sense to create a control on a background thread if you intend to interact with it one way or another from the main thread. Then you will get this exception.
Bottom line: You should create all controls on the same thread and this thread should in most cases be the main UI/dispatcher thread. This will save you a whole lot of trouble.
When you create a control it has to happen in the main UI thread. Currently you are creating the control in another thread and then adding it to another. This will cause an exception.
You need to move the creation of the control to happen inside the invoke so it happens on the main UI thread.
You can't create UI controls in separate threads. The control needs to exist on the UI thread.
You might try having your threaded function do its work through your window's Dispatcher using its .Invoke() methods.
You probably want to make sure that ONLY the manipulation of your UI controls is done with the dispatcher otherwise you'll probably lock up the UI anyway.
public static void AuditMasterSlides(PPT.Presentation pres, Panel panel, MainProofingTaskPaneControl control, CancellationTokenSource cancToken)
{
IDictionary<string,MasterSlide> masterSlides = ReturnMasters(pres, cancToken, control);
this.Dispatcher.Invoke((() => control.ShowAndCollapse(panel));
...
}
As for the STA thread issue, you need to specify that your thread is an STA thread before you start it.
I did this by calling .SetApartmentState() on my thread:
thread1.SetApartmentState(ApartmentState.STA);
thread1.Start();

WPF NotifyPropertyChange from different thread

I have my VM implemented INotifyPropertyChanged interface. I created another thread T for populating a list that I bind to Xaml. After list is populated, I call PropertyChanged in thread T, and my UI got refreshed correctly.
My question is in what case I would need to use Dispatcher? Why I don't need to use Dispatcher in my case? I thought Dispatcher is used when the code in other thread want to notify the changes to the UI thread by enqueuing the changes to the UI refresh queue, such as adding items to ObservableCollection from another thread, and UI thread will then pull data from the queue.
private List<string> _ListData;
public List<String> ListData
{
get
{
if (_ListData == null)
Initialise( () => ListData = ReturnSlow());
return _ListData;
}
set { _ListData = value; }
}
private List<string> ReturnSlow()
{
List<string> Test = new List<string>();
Test.Add("1");
Test.Add("2");
Thread.Sleep(2000);
return Test;
}
public void Initialise(Action initialiser)
{
Task t = new Task(() =>
{
initialiser();
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ListData"));
});
t.Start();
}
Your app has a main UI thread (usually ManagedThreadId==1). If you want to update the UI from an event that gets pull on some other thread you must use the dispatcher. A useful test here is the Dispatcher.CheckAccess() method that returns true if code is on UI thread and false if on some other thread. A typical call looks something like:
using System.Windows.Threading; // For Dispatcher.
if (Application.Current.Dispatcher.CheckAccess()) {
network_links.Add(new NetworkLinkVM(link, start_node, end_node));
}
else {
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(()=>{
network_links.Add(new NetworkLinkVM(link, start_node, end_node));
}));
}
If you're in the main window you can use:
Dispatcher.BeginInvoke(...
If you're in someother context eg a view model then use:
Application.Current.Dispatcher.BeginInvoke(
Invoke vs BeginInvoke
Use Invoke if you want the current thread to wait until the UI thread has processed the dispatch code or BeginInvoke if you want current thread to continue without waiting for operation to complete on UI thread.
MessageBox, Dispatchers and Invoke/BeginInvoke:
Dispatcher.Invoke will block your thread until the MessageBox is dismissed.
Dispatcher.BeginInvoke will allow your thread code to continue to execute while the UI thread will block on the MessageBox call until its dismissed.
CurrentDispatcher vs Current.Dispatcher!
Be ware of Dispatcher.CurrentDispatcher as my understanding of this is that is will return a Dispatcher for the current thread not the UI thread. Generally are you interested in the dispatcher on the UI thread - Application.Current.Dispatcher always returns this.
Additional note:
If you are finding you are having to check dispatcher CheckAccess often then a useful helper method is:
public void DispatchIfNecessary(Action action) {
if (!Dispatcher.CheckAccess())
Dispatcher.Invoke(action);
else
action.Invoke();
}
Which can be called as:
DispatchIfNecessary(() => {
network_links.Add(new NetworkLinkVM(link, start_node, end_node));
});

How to create a control properly from other thread (.net compact framework), with no reference to other control?

I have code that runs in a different thread than the UI's one, and it has to create a control (windows forms). However, I don't have a reference to any control from the UI (that way, I could use myControl.Invoke( methodThatAddsControlToUI ) ). Is there a way to do it in the .net compact framework?
I would be interested in a solution that doesn't use references to other controls, if possible (tracking all created forms, for example, would not be a good workaround, as my code will be in a library). In the full framework version, there is the Application.OpenForms property, but this doesn't exit in the CF.
EDIT:
The main purpose of this is calling a method on the UI thread:
class Worker
{
public MyMethod()
{
// I need to call a method on the UI (this code doesn't run in the UI thread),
// but I don't have any field in this object holding an UI control
// value. So, I can't write myControlField.Invoke(...),
// but I still need to call a method on the UI thread
}
}
Any suggestions?
From a library there's really no way to guarantee your thread context, so your safest bet is to have the consume provide the invoker and leave it to them to ensure it was created in the proper context. Something like this pattern:
class Foo
{
private Control m_invoker;
public Foo()
: this(null)
{
}
public Foo(Control invoker)
{
if (invoker == null)
{
// assume we are created on the UI thread,
// if not bad things will ensue
m_invoker = new Control();
}
else
{
m_invoker = invoker;
}
}
public void Bar()
{
m_invoker.Invoke(new Action(delegate
{
// do my UI-context stuff here
}));
}
}
I'm sorry if this isn't a real answer, but I think it may help:
The reason why WinForms has this approach -- using a Control or Form reference to access a Invoke method that enables you to run code on the UI Thread -- is that the only reason you should have to run a code in the UI Thread is if you are going to write/change the state of UI components.
Of course, if you are going to do that, you must have a reference to a UI component. So you'd have access to its Invoke method. I cannot think of any other reason you'd have to access the UI thread from a component other than to modify a visual element.
It must be invoke ... But invoke have to wait still main thread i mean you not get error this way but this is not exacly working parallel if you want to go more than one process at same time just create more then one thread
Thread thread = new Thread(new delegate_method(method));
thread.start ();
Thread thread2 = new Thread(new delegate_method(method2));
thread.start ();
handle two process same time
void method ()
{
//do something here -- working background Remember can not control any UI control from here
finish_thread()
}
void method2 ()
{
//do something here -- working background Remember can not control any UI control from here
finish_thread()
}
void finish_thread()
{
if(invoke.Required)
{
//Here you have to call delegate method here with UI
BeginInvoke(new delegate_method(finish_thread));
}
else
{
//Now you can control UI thread from here and also you finished background work
//Do something working with UI thread
textBox.Text = "";
}
}
//Declare this in class
public delegate void delege();
//Write this lines when you want to background thread start
Thread thread = new Thread(new ThreadStart(() => {
//Do what you what with backgorund threading , can not use any interface comand here
BeginInvoke(new delege(() => {
//Do here any main thread thread job,this can do interface and control jobs without any error
}));
}));
thread.Start();

Threading & Cross Threading in C#.NET, How do I change ComboBox Data from another Thread?

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.

Calling a method from a backgroundworker

I have a backgroundworker which calls a method in the
DoWork event.
this method accesses a dataset in the UI Thread and it also calls a another method in the UI Thread.
my problem comes in when the method requires access to datasets and methods that exists in the Ui Thread, I get a cross thread operation not valid error.
How can I access Items UI Thread?
Is it possible for me to access it using the backgroundworker or must I use another method of running my method in a background thread
Thanks
You just need to marshal the method call to the UI thread.
On WinForms:
void DoWork(...)
{
YourMethod();
}
void YourMethod()
{
if(yourControl.InvokeRequired)
yourControl.Invoke((Action)(() => YourMethod()));
else
{
//Access controls
}
}
you should use the Dispatcher.Invoke method
for more info have a look at the below link
http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.invoke.aspx
A control created in the UI thread cannot be accessed in another thread in normal fashion. Please create a delegate and invoke the delegate using control.Invoke.
The method sample provided below can be used to enable visibility on a button regardless of the thread context you are in.
private void EnableButtonVisibility( Button btn, bool enable)
{
if ( !btn.InvokeRequired )
{
btn.Visible = enable;
}
else
{
btn.Invoke( new EnableButtonVisibilityHandler( EnableButtonVisibility ), btn, enable );
}
}
delegate void EnableButtonVisibilityHandler( Button btn, bool enable);
You can also achieve the same using Action<Button, bool>

Categories

Resources