Introduction
Modern applications make use of multithreading. One of the reasons is that applications get unresponsive if compute intensive code is executed in the UI-thread. Which is, of course, terrible for the user-experience. Executing such code in a separate (worker) thread solves this problem, but at the same time introduces new problems.
In this article, I will discuss one of these problems: From time to time, there has to be an interaction between the worker and the UI thread, e.g.: the text of a textbox should be updated or a button should be enabled or disabled. This must be done thread-safely, called thread-safe invocation.
Invoke Thread-safe
Thread-safe invocation requires two things: a delegate method and a method that checks if invoke is required and calls the delegate method if true. For example, to set the Button.enabled = false on a Button.click event in a safe-thread way:
delegate void buttonEnabled(bool enabled);
private void setButton_Click(object sender, EventArgs e)
{
buttonEnabledInvoke(false)
}
private void buttonEnabledInvoke(bool enabled)
{
if (setButton.InvokeRequired) setButton.Invoke(new buttonEnabled(buttonEnabledInvoke), enabled);
else setButton.Enabled = enabled;
}
Of course, it is easy to make delegates and methods in such a way for each property, but the disadvantage is that it requires a lot of code which seems quite similar.
The article by Sergiu-Josan describes a solid way to invoke thread safely with one class. The safeInvoke class in this article has three public methods. In this example, we get and set the Button.Enabled property and call the Button.Performclick() method:
bool enabled = (bool)SafeInvoke.GetPropertyValue(setButton, "Enabled"));
SafeInvoke.SetPropertyValue(setButton, "Enabled", false);
SafeInvoke.InvokeMethod(setButton, "PerformClick");
So, this class handles setting and getting all properties and invoking methods of UI-controls in a thread-safe manner.
Shortcomings
The original code could not handle 'controls' like 'ToolStripMenuItem', because this item cannot be cast into a control.
Solution
Use Form.Invoke instead of Control.Invoke in these cases.
The Extended Code
The three previous public functions all have an overload function now. In this example, we get and set ToolStripMenuItem.Enabled property and call the ToolStripMenuItem.PerformClick() method:
bool enabled = (bool)SafeInvoke.GetPropertyValue((Form)this,
demoToolStripMenuItem, "Enabled"));
SafeInvoke.SetPropertyValue((Form)this,
demoToolStripMenuItem,"Enabled", false);
SafeInvoke.InvokeMethod((Form)this,
demoToolStripMenuItem, "PerformClick");
Now follows an example of the getPropertyValue method. In essence, it works the same way as the manual thread-safe invocation method described above. It has a delegate and a method that checks whether invocation is required. However, this function can be used for all controls and all properties. Two cases are implemented in the code:
formControl == null
formControl != null
In the first case, the Control.Invoke method is used and in the second case, the Form.Invoke method is used in order to handle special controls like 'ToolStripMenuItem':
private delegate object PropertyGetInvoker
(Control control, object formControl, string propertyName);
public static object GetPropertyValue
(Control control, object formControl, string propertyName)
{
if (control != null && !string.IsNullOrEmpty(propertyName))
{
if (control.InvokeRequired) {
return control.Invoke(new PropertyGetInvoker
(GetPropertyValue), control,formControl, propertyName);
}
else {
PropertyInfo propertyInfo = GetProperty
(control, formControl, propertyName);
if (propertyInfo != null) {
if (propertyInfo.CanRead) {
if (formControl != null) return propertyInfo.GetValue
(formControl, null); else return propertyInfo.GetValue
(control, null);
}
else {
if (formControl != null) throw new Exception
(formControl.GetType().ToString()
+ "." + propertyName + "
is write-only property.");
else throw new Exception
(control.GetType().ToString()
+ "." + propertyName + "
is write-only property.");
}
}
return null;
}
}
else
{
throw new ArgumentNullException(); }
}
private static PropertyInfo GetProperty
(object control, object formControl, string propertyName)
{
if (control != null && !string.IsNullOrEmpty(propertyName))
{
PropertyInfo propertyInfo;
if (formControl != null) propertyInfo =
formControl.GetType().GetProperty(propertyName);
else propertyInfo = control.GetType().GetProperty(propertyName);
if (propertyInfo == null) {
throw new Exception(control.GetType().ToString()
+ " does not contain '" +
propertyName + "' property.");
}
return propertyInfo;
}
else
{
throw new ArgumentNullException(); }
}
History
- 13th July, 2010: Version 1.0