Background
A truly professional application will make use of worker threads in order to maintain a responsive user interface, and to maximize on hyperthreading or multiple CPUs. However, in .Net this isn't particularly easy because Windows Forms components are not thread safe. In some versions of .Net any attempt to invoke a GUI component's method across threads will be met with an exception telling you it's bad, but usually it just results in strange and unexplainable things happening.
Current Solution
While you can actually call a GUI component from another thread, you cannot just invoke it directly. Instead what you have to do is invoke the GUI component's method indirectly through its inherited Invoke method, a method inherited from the Control class. Unlike other Invoke methods, the Control class's Invoke method makes sure the call is properly synchronized to avoid data corruption.
Calling Control.Invoke requires, as one of the parameters, a delegate for the method you are invoking, which you have to declare and instantiate passing in the actual method, and you will need to also supply an object array of parameters.
The Problem
The problem with this solution is that you have to declare and instantiate a delegate for every method you need to call cross thread, which can be mundane and prohibitive at best. This the kind of code you would have to write:
public delegate void OutputMessageDelegate(string someMessage);
...
OutputMessageDelegate del = new OutputMessageDelegate(someForm.OutputMessage);
object[] paramList = new object[] { "This is a message" };
someForm.Invoke(del, paramList);
Making It Easier
Wouldn't it be nice if you could just write this?
SafeInvokeHelper.Invoke(someForm, "OutputMessage", "This is a message");
And this would take care of declaring and instantiating the delegate and making sure it's invoked in the UI thread.
This is much closer to what you would write naturally:
someForm.OutputMessage("This is a message");
This is made possible with the class I have written below. You can include this class into your project and use the SafeInvokeHelper.Invoke static method to make calls cross-thread without worrying about thread-safety or creating new delegates for each method you need to invoke.
The Code
using System;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;
...
public class SafeInvokeHelper
{
static readonly ModuleBuilder builder;
static readonly AssemblyBuilder myAsmBuilder;
static readonly Hashtable methodLookup;
static SafeInvokeHelper()
{
AssemblyName name = new AssemblyName();
name.Name = "temp";
myAsmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
builder = myAsmBuilder.DefineDynamicModule("TempModule");
methodLookup = new Hashtable();
}
public static object Invoke(System.Windows.Forms.Control obj, string methodName, params object[] paramValues)
{
Delegate del = null;
string key = obj.GetType().Name + "." + methodName;
Type tp;
lock (methodLookup) {
if (methodLookup.Contains(key))
tp = (Type)methodLookup[key];
else
{
Type[] paramList = new Type[obj.GetType().GetMethod(methodName).GetParameters().Length];
int n = 0;
foreach (ParameterInfo pi in obj.GetType().GetMethod(methodName).GetParameters()) paramList[n++] = pi.ParameterType;
TypeBuilder typeB = builder.DefineType("Del_" + obj.GetType().Name + "_" + methodName, TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.Public | TypeAttributes.Sealed, typeof(MulticastDelegate), PackingSize.Unspecified);
ConstructorBuilder conB = typeB.DefineConstructor(MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, new Type[] { typeof(object), typeof(IntPtr) });
conB.SetImplementationFlags(MethodImplAttributes.Runtime);
MethodBuilder mb = typeB.DefineMethod( "Invoke", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, obj.GetType().GetMethod(methodName).ReturnType, paramList );
mb.SetImplementationFlags( MethodImplAttributes.Runtime );
tp = typeB.CreateType();
methodLookup.Add(key, tp);
}
}
del = MulticastDelegate.CreateDelegate(tp, obj, methodName);
return obj.Invoke(del, paramValues);
}
}
How It Works
As I explained, the Control.Invoke method that provides thread safe access to GUI component methods requires a delegate. Because the delegate is a special, runtime class there's no way to create them dynamically using straight C#.
To work around this we dynamically generate the delegate type using the reflection emit namespace methods. We force this new type to inherit from MultiCastDelegate, and we add an 'Invoke' method with the appropriate method signature. The constructor and method have a special implementation flag 'Runtime' that tells the CLR that there is no actual body to the method and it is purely a signature.
Because the delegate construction is a time-consuming process we cache the created delegate in a hashtable. The next time the same method is invoked we instead take the delegate straight from the cache and use that.
Known Issues
- Methods must be public.
- Overloaded methods are not supported.
Updates
- Sep 8th 05: Methods can now have return values.
- Nov 13th 05: Fixed bug that emerged when invoking same method signature on different instances.
Let me know (by adding a comment) if you find any bugs.