91

I have recently started programming in WPF and bumped into the following problem. I don't understand how to use the Dispatcher.Invoke() method. I have experience in threading and I have made a few simple Windows Forms programs where I just used the

Control.CheckForIllegalCrossThreadCalls = false;

Yes I know that is pretty lame but these were simple monitoring applications.

The fact is now I am making a WPF application which retrieves data in the background, I start off a new thread to make the call to retrieve the data (from a webserver), now I want to display it on my WPF form. The thing is, I cannot set any control from this thread. Not even a label or anything. How can this be resolved?

Answer comments:
@Jalfp:
So I use this Dispatcher method in the 'new tread' when I get the data? Or should I make a background worker retrieve the data, put it into a field and start a new thread that waits till this field is filled and call the dispatcher to show the retrieved data into the controls?

Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
D. Veloper
  • 1,497
  • 5
  • 16
  • 24
  • That CheckForIllegalCrossThreadCalls is awesome. Wish I knew that before for quick "who cares" applications – Gaspa79 Sep 01 '20 at 14:55

4 Answers4

192

The first thing is to understand that, the Dispatcher is not designed to run long blocking operation (such as retrieving data from a WebServer...). You can use the Dispatcher when you want to run an operation that will be executed on the UI thread (such as updating the value of a progress bar).

What you can do is to retrieve your data in a background worker and use the ReportProgress method to propagate changes in the UI thread.

If you really need to use the Dispatcher directly, it's pretty simple:

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => this.progressBar.Value = 50));
Rob
  • 11,446
  • 7
  • 39
  • 57
japf
  • 6,598
  • 3
  • 33
  • 30
  • 26
    You can get rid of the 'new Action(' part, and simply use a lambda expression: DispatcherPriority.Background, () => this.progressBar.Value = 50 – jrista Oct 29 '09 at 14:55
  • 2
    Yeah don't know why I put an Action here :p – japf Oct 29 '09 at 15:09
  • So I use this Dispatcher method in the 'new tread' when I get the data? Or should I make a background worker retrieve the data, put it into a field and start a new thread that waits till this field is filled and call the dispatcher to show the retrieved data into the controls? – D. Veloper Oct 30 '09 at 08:17
  • Hmm I don't seem to be able to access the Dispatcher in this way: 'System.Windows.Forms.Application' does not contain a definition for 'Current'. – Carsten May 30 '13 at 22:52
  • 2
    @Carsten This answer is for WPF applications which use the System.Windows.Application class. – joshuapoehls Jul 30 '13 at 18:10
  • 11
    @jrista: Can you really? I'm getting [CS1660](http://msdn.microsoft.com/en-us/library/hy74she2%28v=vs.80%29.aspx) when trying without `new Action(...)`. – O. R. Mapper Aug 01 '13 at 09:02
  • @O.R.Mapper: A delegate simply describes the required method signature. So long as what you pass in matches that signature, it should be possible. I rarely ever create actual delegate types these days...I either simply pass in the literal name of the function that matches what the delegate describes, or create a lambda that provides the necessary signature. – jrista Aug 01 '13 at 15:45
  • 6
    @jrista: In general, true - though [this article](http://visualstudiomagazine.com/articles/2009/02/01/use-lambda-expressions-for-abstract-delegates.aspx) explains why it doesn't work in the case of parameterless methods such as the ones passed to `BeginInvoke` and instead compiler error CS1660 is yielded. – O. R. Mapper Aug 01 '13 at 20:59
  • `BeginInvoke()` can be replaced by the newer `InvokeAsync()`, which won't have the CS1660 issue. – Arkane Sep 10 '21 at 08:51
  • Doesn't work, sets it after the method returns. Is there a InvokeNOW method? – Paul McCarthy Jul 01 '22 at 15:40
34

japf has answer it correctly. Just in case if you are looking at multi-line actions, you can write as below.

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => { 
    this.progressBar.Value = 50;
  }));

Information for other users who want to know about performance:

If your code NEED to be written for high performance, you can first check if the invoke is required by using CheckAccess flag.

if(Application.Current.Dispatcher.CheckAccess())
{
    this.progressBar.Value = 50;
}
else
{
    Application.Current.Dispatcher.BeginInvoke(
      DispatcherPriority.Background,
      new Action(() => { 
        this.progressBar.Value = 50;
      }));
}

Note that method CheckAccess() is hidden from Visual Studio 2015 so just write it without expecting intellisense to show it up. Note that CheckAccess has overhead on performance (overhead in few nanoseconds). It's only better when you want to save that microsecond required to perform the 'invoke' at any cost. Also, there is always option to create two methods (on with invoke, and other without) when calling method is sure if it's in UI Thread or not. It's only rarest of rare case when you should be looking at this aspect of dispatcher.

prime23
  • 3,362
  • 2
  • 36
  • 52
Yogee
  • 1,412
  • 14
  • 22
6

When a thread is executing and you want to execute the main UI thread which is blocked by current thread, then use the below:

current thread:

Dispatcher.CurrentDispatcher.Invoke(MethodName,
    new object[] { parameter1, parameter2 }); // if passing 2 parameters to method.

Main UI thread:

Application.Current.Dispatcher.BeginInvoke(
    DispatcherPriority.Background, new Action(() => MethodName(parameter)));
Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
Gopi Rao
  • 61
  • 1
  • 2
0

The @japf answer above is working fine and in my case I wanted to change the mouse cursor from a Spinning Wheel back to the normal Arrow once the CEF Browser finished loading the page. In case it can help someone, here is the code:

private void Browser_LoadingStateChanged(object sender, CefSharp.LoadingStateChangedEventArgs e) {
   if (!e.IsLoading) {
      // set the cursor back to arrow
      Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
         new Action(() => Mouse.OverrideCursor = Cursors.Arrow));
   }
}
nrod
  • 398
  • 6
  • 17