August 2005 - Posts

SafeInvoke: Making GUI Thread Programming Easier in C#

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.

Avoiding the Hassle of XmlNamespaceManager

The XML DOM's handling of namespaces is an inconvenience at best. Anyone who's had to use XPath over an existing document in .Net will know the hassles they hit when they try to use XPaths that include namespace prefixes. The bottom line is that you need to manually tell the DOM about the prefixes and their associated URIs even though the information is usually defined already in the document you are loading.

Take a document that starts with something like: (angle brackets replaced with square brackets for formatting)

[my:root xmlns:my="http://somenamespaceuri.com"]
    [my:root xmlns:my=
http://somenamespaceuri.com]
        [my:someElement]Hello[/my:someElement]

In order to load this document and run an XPath over it, you'll need to also create a namespace manager and add the 'my' namespace declaration. What makes this inconvenient is that the information is already in the XML document, but the .Net implementation of the XML DOM just ignores it. I suppose there are infrequent cases when the namespace declarations are somewhere other than the root, and may even be redefined within the document, making it difficult to locate. However most of the time this isn't the case, so I think there's something we can do to make it easier.

Various articles/documents on the web tell you to write some code like this to work around the problem.

XmlDocument doc = new XmlDocument( ) ;
doc.Load ( "myfile.xml") ;
XmlNamespaceManager nsmgr = new XmlNamespaceManager( doc.NameTable) ;
nsmgr.Add ( "my", "http://somenamespaceuri.com") ;
XmlNode result = doc.SelectSingleNode( "//my:someElement", nsmgr) ;

The problems with this is that a) it's duplication, the declarations are already in the XML, and b) it's not extensible - user supplied XPaths won't work if the document changes and new namespaces are added.

I've come up with a possible workaround. This snippet of code automatically generates the XmlNamespaceManager based on the xmlns attributes on the root of the document (assuming that's where they are). Unfortunately the .Net DOM doesn't let you query for the xmlns attributes through XPath, it complains that they're system attributes and can't be referenced. But there is a way to get at them manually.

This is the code:

public static XmlNamespaceManager CreateNsMgr( XmlDocument doc)
{
    XmlNamespaceManager nsmgr = new XmlNamespaceManager( doc.NameTable) ;
    foreach ( XmlAttribute attr in doc.SelectSingleNode( "/*") .Attributes)
        if ( attr.Prefix == "xmlns") nsmgr.AddNamespace( attr.LocalName, attr.Value) ;
    return nsmgr;
}

This lets you write code without having to manually add in each namespace. For example the example code would be rewritten as:

XmlDocument doc = new XmlDocument( ) ;
doc.Load ("myfile.xml") ;
XmlNode result = doc.SelectSingleNode ("//my:someElement", CreateNsMgr(doc) ) ;

Note that it does assume that the namespace declarations are on the root element.