Search code examples
c#.netwinformsui-threadsynchronizationcontext

SynchronizationContext.Send to call a Func returning a result


I have in a current project a simple component that can be dropped from the visual studio toolbox onto Forms and UserControls in WinForms application.

It worked fine as is - and simply wraps the Send and Post methods of a SynchronizationContext captured during ISupportInitialize.BeginInit (UI Context of owning Form/Control).

Here is a the existing code for the component in it's most simple form.

public class SyncContextComponent : Component, ISupportInitialize
{
    private SynchronizationContext _context;

    public SyncContextComponent() { }
    public SyncContextComponent(IContainer container)
    {
        container.Add(this);
    }

    void ISupportInitialize.BeginInit()
    {
        _context = SynchronizationContext.Current; // context of creator
    }
    void ISupportInitialize.EndInit() { }

    /// <summary>
    /// Dispatches an <b>asynchronous</b> message to <see cref="Context"/>
    /// </summary>
    /// <param name="action">The delegate to call</param>
    /// <example>
    /// uiContext.Post(() => this.WorkOnUiControls());
    /// </example>
    public void Post(Action action)
    {
        _context.Post(new SendOrPostCallback(_ => action()), null);
    }

    /// <summary>
    /// Dispatches an <b>synchronous</b> message to <see cref="Context"/>
    /// </summary>
    /// <param name="action">The delegate to call</param>
    /// <example>
    /// uiContext.Send(() => this.WorkOnUiControls());
    /// </example>
    public void Send(Action action)
    {
        _context.Send(new SendOrPostCallback(_ => action()), null);
    }
}

I now needed to extend this to support returning values, and was looking at the various ways...

I'm interested in understanding the implications of the following 3 ways of handling this, and if there is a "better" way.

Note: the example I'm using is to call a function that takes 1 argument arg1, and returns TResult, i.e. Func<T1, TResult>. I intend to extend this once I know the best methodology, so I have overloads that cover from Func<TResult> up to perhaps 9args of Func<T1,T2....T9,TResult>, as well as Action overloads.

Send0: The first way - was a simple extension of what I was already doing:

    public TResult Send0<T1, TResult>(Func<T1, TResult> func, T1 arg1)
    {
        TResult retval = default(TResult);
        _context.Send(new SendOrPostCallback((x) =>
        {
            retval = func(arg1);
        })
        , null);
        return retval;
    }

Send1: The second way - was prompted by research on this and looking at other info on stackoverflow, that hinted that I should probably get the result by using the state arg. I figured that if that was the case - I'd probably need to do the same with the input args as well, so I got the following:

    private class SendFuncState1<T1, TResult>
    {
        public TResult Result { get; set; }
        public T1 Arg1 { get; set; }
    }
    public TResult Send1<T1, TResult>(Func<T1, TResult> func, T1 arg1)
    {
        SendFuncState1<T1, TResult> state = new SendFuncState1<T1, TResult>()
        {
            Arg1 = arg1
        };
        _context.Send(new SendOrPostCallback((x) =>
        {
            var state0 = (SendFuncState1<T1, TResult>)x;
            state0.Result = func(state0.Arg1);
        })
        , state);
        return state.Result;
    }

Send2: The third try - I figured since I was passing the args, perhaps I should be passing the Func itself as well? Anyway - I got the following:

    private class SendFuncState2<T1, TResult>
    {
        public Func<T1, TResult> Func { get; set; }
        public TResult Result { get; set; }
        public T1 Arg1 { get; set; }
    }
    public TResult Send2<T1, TResult>(Func<T1, TResult> func,
        T1 arg1)
    {
        SendFuncState2<T1, TResult> state = new SendFuncState2<T1, TResult>()
        {
            Func = func,
            Arg1 = arg1
        };
        _context.Send(new SendOrPostCallback((x) =>
        {
            var state0 = (SendFuncState2<T1, TResult>)x;
            state0.Result = state0.Func(state0.Arg1);
        })
        , state);
        return state.Result;
    }

So, I then put this into a small test app, and tested each method from a background thread updating WinForms control and did some Stopwatch on 10k iterations of each method to test speed of various approaches (just using strings as the args and return values) They all worked fine and had the same performance in that simple test. Not sure what it would be like when using non-trivial input/output types though.

So - is Send0 fine - and the rest just irrelevant / mess for no reason? Or should I be considering one of the others - or something else?


Solution

  • They all worked fine

    Yes, they all work. Use the simplest one - the first. The compiler turns the first into the second for you (basically). Lambdas do not exist at the CLR level. A hidden closure class is created for you and local variable accesses are rewritten to access fields of that class.

    had the same performance

    I'm sure they didn't but running a Windows Message through the message loop is so slow that it dominates the simple operations that you are doing completely. Pointless to optimize this.