Extension of safeInvoke
Extention of the 'safeInvoke' class by Sergiu Josan
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:
// declare a button-enabled delegate
delegate void buttonEnabled(bool enabled);
//handles setbutton click event
private void setButton_Click(object sender, EventArgs e)
{
buttonEnabledInvoke(false)
}
//method that safe-invokes the button enabled property
private void buttonEnabledInvoke(bool enabled)
{
if (setButton.InvokeRequired) //if invoke required ->invoke and call same method
setButton.Invoke(new buttonEnabled(buttonEnabledInvoke), enabled);
else // else set enabled directly
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:
// get a property value in a safe-thread way
bool enabled = (bool)SafeInvoke.GetPropertyValue(setButton, "Enabled"));
// set a property value in a safe-thread way
SafeInvoke.SetPropertyValue(setButton, "Enabled", false);
// Invoke a method in a safe-thread way
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:
// get a property value in a safe-thread way
bool enabled = (bool)SafeInvoke.GetPropertyValue((Form)this,
demoToolStripMenuItem, "Enabled"));
// set a property value in a safe-thread way
SafeInvoke.SetPropertyValue((Form)this,
demoToolStripMenuItem,"Enabled", false);
// Invoke a method in a safe-thread way
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
':
//delegate for getting a property
private delegate object PropertyGetInvoker
(Control control, object formControl, string propertyName);
//public method for getting a property
public static object GetPropertyValue
(Control control, object formControl, string propertyName)
{
if (control != null && !string.IsNullOrEmpty(propertyName))
{
if (control.InvokeRequired) //if invoke required -> invoke
{
return control.Invoke(new PropertyGetInvoker
(GetPropertyValue), control,formControl, propertyName);
}
else //else getting the property directly
{
PropertyInfo propertyInfo = GetProperty
(control, formControl, propertyName);
if (propertyInfo != null) //property exists
{
if (propertyInfo.CanRead) //property can be read
{
if (formControl != null) //special
//'control'
return propertyInfo.GetValue
(formControl, null); //return
// the value
else //normal control
return propertyInfo.GetValue
(control, null);
}
else // throw an exception that the
// property cannot be read
{
if (formControl != null) //special
//'control'
throw new Exception
(formControl.GetType().ToString()
+ "." + propertyName + "
is write-only property.");
else //normal control
throw new Exception
(control.GetType().ToString()
+ "." + propertyName + "
is write-only property.");
}
}
return null;
}
}
else
{
throw new ArgumentNullException(); //one of the arguments is null
}
}
// getting property info
private static PropertyInfo GetProperty
(object control, object formControl, string propertyName)
{
if (control != null && !string.IsNullOrEmpty(propertyName))
{
PropertyInfo propertyInfo;
if (formControl != null) //special 'control'
propertyInfo =
formControl.GetType().GetProperty(propertyName);
else //normal control
propertyInfo = control.GetType().GetProperty(propertyName);
if (propertyInfo == null) //property does not exist -> throw exception
{
throw new Exception(control.GetType().ToString()
+ " does not contain '" +
propertyName + "' property.");
}
return propertyInfo;
}
else
{
throw new ArgumentNullException(); //one of the arguments is null
}
}
History
- 13th July, 2010: Version 1.0