62

I have been trying to follow some WCF Data Services examples and have the following code:

private void OnSaveCompleted(IAsyncResult result)
    {
        Dispatcher.BeginInvoke(() =>
        {
            context.EndSaveChanges(result);
        });
    }

Which is called by the following:

this.context.BeginSaveChanges(SaveChangesOptions.Batch, this.OnSaveCompleted, null);

Now I am getting a little confused here. Firstly, the first bit of code is showing a syntax error of

Argument type lambda expression is not assignable to parameter type System.Delegate

So instead of blindly trying to follow the example code, I tried to understand what was going on here. Unfortunately, I am struggling to understand the error plus what is actually happening. Can anyone explain?

I feel a bit stupid as I am sure this is easy.

Pang
  • 9,564
  • 146
  • 81
  • 122
Jon Archway
  • 4,852
  • 3
  • 33
  • 43

3 Answers3

118

The problem is that the compiler doesn't know what kind of delegate you're trying to convert the lambda expression to. You can fix that either with a cast, or a separate variable:

private void OnSaveCompleted(IAsyncResult result)
{        
    Dispatcher.BeginInvoke((Action) (() =>
    {
        context.EndSaveChanges(result);
    }));
}

or

private void OnSaveCompleted(IAsyncResult result)
{
    Action action = () =>
    {
        context.EndSaveChanges(result);
    };
    Dispatcher.BeginInvoke(action);
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    Thanks, but now I am getting "Cannot access non-static method 'BeginInvoke' in static context. I am more confused now, as this isn't a static method? – Jon Archway Sep 21 '10 at 13:51
  • 1
    @Jon: It thinks you're trying to use BeginInvoke as a static method on the Dispatcher class - whereas you want to use the Dispatcher *property* and then call BeginInvoke on the relevant instance. My guess is that this isn't in an appropriate class with a Dispatcher property. Having just seen that this is WCF, I'm not sure sure where you'd get a Dispatcher from. I'm more used to using it from WPF and Silverlight. – Jon Skeet Sep 21 '10 at 14:05
  • 1
    This is actually on a ViewModel class in a WPF application – Jon Archway Sep 21 '10 at 14:16
  • The ViewModel typically has no knowledge of the view, much less an associated dispatcher. You could go about using Dispatcher.CurrentDispatcher, but I strongly advise against it (you could easily end up in the wrong thread and the delegate will never be invoked); the best way I think would be to use something like MVVMLight's Messenger and send a message to the view - the message could contain the Action and the View could invoke it using its dispatcher. – Alex Paven Sep 21 '10 at 14:45
  • 1
    @Jon: It would *definitely* be worth updating the question to reflect that. The WCF part is irrelevant, but the WPF part is highly relevant. As for getting the dispatcher to the ViewModel - you could use the messenger as Alex suggested, or you could inject the Dispatcher nito the ViewModel... not as a Dispatcher, but in terms of a wrapper around it, implementing your own interface. That way you can test that you're doing the right thing in terms of thread safety. I've done this before, and it's worked well. – Jon Skeet Sep 21 '10 at 14:56
  • @Jon (Skeet) - Interesting idea; however I vaguely remember running into problems with something similar because under a test environment like MSTest (console app), the Dispatcher isn't started automatically and you must jump through some hoops to get your delegates to be invoked correctly. However I might be confusing it with something else... not sure anymore, it's late and I'm tired. – Alex Paven Sep 21 '10 at 16:08
  • @my previous comment: Actually nevermind, of course if you inject the dispatcher using an interface you can mock it while testing. – Alex Paven Sep 21 '10 at 16:39
  • What was the solution? I'm having the same problem where I get: An object reference is required for the non-static field, method, or property 'System.Windows.Threading.Dispatcher.BeginInvoke(System.Delegate, params object[])'. Can't use an outside framework or nuGet package. – Kala J Aug 01 '14 at 14:06
  • @KalaJ: Well presumably you're within a static method... you have to call it on a particular dispatcher. Your problem is entirely different to the one in this question, where `Dispatcher` means the `Dispatcher` *property* within a control. – Jon Skeet Aug 01 '14 at 14:59
  • @JonSkeet, Nope my method is not static. The solution I found was, I had to get the dispatcher from the UI thread then pass it to my task. – Kala J Aug 01 '14 at 15:31
  • @Kala: Well you have to get it from a UI *component*... Not from a thread. – Jon Skeet Aug 01 '14 at 15:39
  • @JonSkeet, What's the difference? The way I thought of it was that I have a UI thread and a Background thread. I had to get it from the UI thread and pass it to the background thread. – Kala J Aug 01 '14 at 15:43
  • @KalaJ: There's all the difference in the world between a component and a thread. But so long as you've got your solution, that's probably fine. If you want to know more, ask a new question. – Jon Skeet Aug 01 '14 at 16:06
  • oops, just had the same problem (with the non-static mentioned above) getting a CS0120 "An object reference is required for the non-static field, method, or property" when doing public void Speak(string text) { Dispatcher.BeginInvoke(new Action(() => speechSynthesizer.Speak(text))); } - TURNS OUT I needed to do public void Speak(string text) { Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => speechSynthesizer.Speak(text))); }. I think Visual Studio 2015 should add an extra lightbulb hint for this one, bit hard to catch – George Birbilis Sep 05 '15 at 21:28
  • Seems people get confused and do Dispatcher.BeginInvoke instead of Dispatcher.CurrentDispatcher.BeginInvoke. This is because there is a Dispatcher property available at WPF so they try to reuse code pattern they remember in code outside of a WPF window – George Birbilis Sep 05 '15 at 21:36
16

Answer by Jon Skeet is very good but there are other possibilities. I prefer "begin invoke new action" which is easy to read and to remember for me.

private void OnSaveCompleted(IAsyncResult result)
{       
    Dispatcher.BeginInvoke(new Action(() =>
    {
        context.EndSaveChanges(result);
    }));
}

or

private void OnSaveCompleted(IAsyncResult result)
{       
    Dispatcher.BeginInvoke(new Action(delegate
    {
        context.EndSaveChanges(result);
    }));
}

or

private void OnSaveCompleted(IAsyncResult result)
{       
    Dispatcher.BeginInvoke(new Action(() => context.EndSaveChanges(result)));
}
CoperNick
  • 2,413
  • 2
  • 21
  • 26
6

If your method does not require parameters, this is the shortest version I've found

Application.Current.Dispatcher.BeginInvoke((Action)MethodName); 
Alon Amsalem
  • 187
  • 1
  • 13