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
I completed my master in electrical engineering at the University of Twente in the Netherlands in 2007. My special interest is software/hardware application which can be used for health purposes.
Currently, I am PhD-student at the Radboud University in Nijmegen, The Netherlands. I am involved in a project in which an monitoring and feedback application is developed for daily-life usage.
My personal interests: bicycling, salsa dancing and gardening