September 2005 - Posts

SafeInvoke: Making GUI Thread Programming Easier in C# - Part 2

A few days ago I posted a way to make thread-safe calls to the GUI without having to create delegates.

I also use another shortcut when developing multi-threaded applications that I"ll explain below.

There are several options for spawning a new thread. The most common use is through the thread pool, which automatically queues a function call to be invoked on a new thread as soon as one becomes available. Another is to manually create a thread and have it run a function you provide, in which the thread lives for the duration of your function call.

With both of these approaches your method needs to match the signature of a simple delegate: WaitCallback requires a function that just takes a simple state object, and ThreadStart takes a function with no parameters.

The problem is that you usually need to pass more than just one parameter to the method. The usual workaround is to have the necessary data passed in through member variables that are common to both methods. This solution, though, can often lead to problems because a) they lead to confusion because the member variables have no use other than passing this data to a function, and therefore shouldn't really be member variables, b) there's no guarantee that these variables won't be accessed by the GUI thread while the worker thread is running.

What we need is the ability to pass more data to thread function more easily.

My solution lets you do this through a form of late-binding. With this solution you can write code such as:

SafeInvokeHelper.StartThread(this, "SendRequest", userName, password, server);

to start a new thread and run SendRequest in that thread. In this example "SendRequest" is the name of the method, and the remaining parameters are arguments to that function.

The alternative to this involves either declaring userName, password and server as member variables, and settings those before starting the thread, or creating a class specifically to hold those values and passing that in as the state object. Either way it's a whole lot more code to write.

And, using the SakeInvoker.Invoke method I provided in my last post, you can easily signal the function's completion at the end of the 'SendRequest' function using the straightforward:

SafeInvokeHelper.Invoke(this, "FinishedSendRequest", resultData1, resultData2);

All in all - a significantly easier and safer mechanism for implementing a multithreaded GUI application!

This solution works by using a generic method that matches the simple delegate. The generic method then uses reflection to find the specified method and invoke the function using the given parameters.

Here's the code. It's pretty simple. You can easily incorporate this into any of your projects, along with the code from the last post.

public sealed class SafeInvokeHelper
{
    public static void GenDelegate(object state)
    {
        object[] parameters = (object[])state;
        object target = parameters[0];
        string methodName = parameters[1].ToString();
        object[] args = (object[])parameters[2];
        System.Reflection.MethodInfo method = target.GetType().GetMethod(methodName);
        method.Invoke(target, args);
    }

    public static void StartThread(object target, string methodName, params object[] parameters)
    {
        object[] list = new object[] { target, methodName, parameters };
        System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(GenDelegate), list);
    }
}

Known issues:

  • Methods must be public
  • No parameter/method checks done at compile time.
  • Reflection can be quite slow.

If you find any bugs please let me know.