65.9K
CodeProject is changing. Read more.
Home

Extension of safeInvoke

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (3 votes)

Jul 13, 2010

CPOL

2 min read

viewsIcon

21290

downloadIcon

324

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