Control.InvokeRequired, DelegateMarshaler and Anonymous Methods

A couple weeks ago I ran across an awesome post about replacing all of those tests for Control.InvokeRequired with a more programmer-friendly approach.

Kevin was kind enough to provide a sample code download, fully commented and everything.  I learned a lot from reading through his code.

I really like his idea of a DelegateMarshaler that can be created on the UI thread and used to marshal delegate calls to the UI thread if they are called from a different thread.

This approach is very similar to the BackgroundWorker, but is much, much more flexible.  The BackgroundWorker, in a sense, locks you into the pattern of providing a background worker thread that needs to update a progress bar on the main UI.  You can do operations other than updating a progress bar, but you need to get creative with the types of objects passed back.

I made a few minor improvements on the DelegateMarshaler class and stripped it down to its bare minimum.  Here’s my version:

public class DelegateMarshaler

{

 

private SynchronizationContext _synchronizationContext = null;

 

public static DelegateMarshaler Create()

{

     if (SynchronizationContext.Current == null)

    {

        throw new InvalidOperationException("No SynchronizationContext exists for the current thread.");

    }

    return new DelegateMarshaler(SynchronizationContext.Current);

}

 

private DelegateMarshaler(SynchronizationContext synchronizationContext)

{

    _synchronizationContext = synchronizationContext;

}

 

private bool IsMarshalRequired

{

    get

    {

        return _synchronizationContext != SynchronizationContext.Current;

    }

}

public void Invoke(Action action)

{

    if (!this.IsMarshalRequired)

    {

        action();

    }

    else

    {

        _synchronizationContext.Send(o => action(), null);

    }

}

 

}

 

Basically you use it by calling the static Create method while on the UI thread, and then any calls from any other threads that are invoked will be marshaled back to the UI thread.  Very cool idea, and a slick implementation.

 

As an example, let’s say that we have a form with a text box and a progress bar.  We want to write log messages to the text box for a long computation while at the same time updating the progress bar.  This is possible using a BackgroundWorker, but takes a lot of extra plumbing to be able to handle the log messages.

 

Using the DelegateMarshaler makes this incredibly simple, and the code reads from top to bottom exactly as it’s being executed:

 

private void btnStart_Click(object sender, EventArgs e)

{

    DelegateMarshaler marshaler = DelegateMarshaler.Create();

 

    ThreadPoolHelper.QueueUserWorkItem(() =>

        {

            marshaler.Invoke(() =>

                {

                    txtOutput.AppendText("Starting long-running computation…");

                    txtOutput.AppendText(Environment.NewLine);

                });

            for (int i = 0; i < 100; i++)

            {

                marshaler.Invoke(

                () =>

                {

                    txtOutput.AppendText(String.Format("Percent: {0}", i));

                    txtOutput.AppendText(Environment.NewLine);

                    prgProgress.Value = i;

                });

                Thread.Sleep(50);

            }

            marshaler.Invoke(() =>

            {

                txtOutput.AppendText("Done!");

                txtOutput.AppendText(Environment.NewLine);

            });

        });

}

Creating the DelegateMarshaler "sticks" it to the UI thread.   Then we can queue up an anonymous method (using the ThreadPoolHelper) to execute on another thread from the thread pool that then invokes methods (text box updates and progress bar updates) on the UI thread.

Cool!  Like I said, I really like this approach and think that it produces some very clean code.

Hope someone finds that useful!


You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

AddThis Social Bookmark Button

6 Responses to “Control.InvokeRequired, DelegateMarshaler and Anonymous Methods”

  1. I tried to do this but in the Invoke method Action is a generic and wants a and then it complains because action wants a parameter (rather than ()) and “o” is not declared. Have I missed a using?

  2. Hi jhunter,

    Hmm, that’s weird. Can you post the line of code where the compiler is complaining?

    Also, which version of C# are you targeting? If you’re only using C# 2.0, then the syntax for the lambda expression (“() => …”) isn’t supported. Lambda expressions were a C# 3.0 addition, though you can accomplish the same thing in C# 2.0 using “delegate() { }”, etc.

  3. […] Control.InvokeRequired, DelegateMarshaler and Anonymous Methods […]

  4. Bill Tribley Says:

    I am using c# 2.0. So, instead of

    += (o,args)=>{ block }

    use

    += delegate(o,args){ block }

    correct?

  5. Hi Bill,

    Yes, if you’re using C# 2.0, then you can’t use the “lambda expression” anonymous method syntax. I actually think the correct line would be

    += delegate(object o, DataReceivedEventArgs args) { … }

    I think you still need to explicitly declare the types.

  6. […] long story short: I found this cool thing that lets you use C# 3’s lambdas to describe the work you want to do on the GUI thread. […]

Leave a Reply