difference between SendOrPostCallback and Action in multithreaded environment?

kris picture kris · Nov 30, 2010 · Viewed 7.1k times · Source

I'm fairly new to working with threads. I was trying to set a DependencyProperty's value:

    public States State
    {
        get { return (States)GetValue(StateProperty); }
        set
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Background,
                //(SendOrPostCallback)delegate { SetValue(StateProperty, value); }, //works
                (Action)(()=> SetValue(StateProperty, value)), //doesnt
                value);
        }
    }
    public static readonly DependencyProperty StateProperty =
        DependencyProperty.Register("State", typeof(States), typeof(FTPDownload), new UIPropertyMetadata(States.Idle));

I realized the hard way that in the setter you have to use SendOrPostCallback (as it provides an argument when calling the method). it does NOT work with Action (because of the missing argument. And, wpf is really a bitch about it, debugging and finding the cause of the TargetParameterCountException with "no source available" and no clue at all.

Why do I have to use SendOrPostCallback there? and how should I know that in this case this is the right one? Because actually calling the setter works via:

Dispatcher.BeginInvoke((Action)(()=>State=States.Updating), null);

and using the SendOrPostCallback instead of course results in a TargetParameterCountException..

Just wondering if seemingly inconsistent thing like that are just common knowledge? Feeling a bit lost here, at least since googling around with SendOrPostCallback, Action and BeginInvoke as keywords had no meaningful results.

Answer

Ani picture Ani · Nov 30, 2010

The relevant pieces of information:

1.The overload of Dispatcher.BeginInvoke that you are using is:

public DispatcherOperation BeginInvoke(
    DispatcherPriority priority,
    Delegate method,
    Object arg
)

method: A delegate to a method that takes one argument, which is pushed onto the Dispatcher event queue.

2.The SendOrPostCallBack delegate is declared as:

public delegate void SendOrPostCallback(object state)

3.As for Action:

public delegate void Action()

Clearly, the SendOrPostCallBack delegate is compatible since it takes a single argument but Action is not, since it is parameterless.

Of course, you can use the Action<T> delegate, which does take a single argument, if you prefer:

Dispatcher.BeginInvoke(DispatcherPriority.Background,
                        new Action<States>(arg => SetValue(StateProperty, arg)),
                        value);

Alternatively, you can use a different overload of Dispatcher.BeginInvoke that expects an argument that is of a delegate-type that takes no arguments, and get the C# compiler to do the dirty work for you in the closure:

Dispatcher.BeginInvoke(DispatcherPriority.Background,
                        new Action(() => SetValue(StateProperty, value));

Notice that value is a captured variable, so please be careful.

(Also, this answer doesn't deal with any thread-safety issues, only about the delegate signatures involved.)