views:

990

answers:

2

Hi guys

I'm a newbie with WCF services, and I'm trying to figure out if is there a better way to update a WPF UI element (like a Label control) when I'm calling asynchronously my WCF service.

Here's a piece of code:

    private void button1_Click(object sender, RoutedEventArgs e)
    {

        int result;
        CalculatorServiceClient proxy = new CalculatorServiceClient();
        AsyncCallback addOperation = (async_result) =>
        {
            result = proxy.EndAdd(async_result);

            Dispatcher.Invoke(DispatcherPriority.Normal,
                  new Action(
                      delegate()
                      {
                        label1.Content = result.ToString();
                      }
                  )
            );
            proxy.Close();
        };
        proxy.BeginAdd(Convert.ToInt32(txtNumber1.Text), Convert.ToInt32(txtNumber2.Text), addOperation, null);

    }

As you can see, I'm updating label1.Content with an asynchronous result, obtained by an AsyncCallback.

My question is, is there a better or more correct way to refresh an UI control inside this asynchronous callback operation?

thanks in advance!

+2  A: 

The Dispatcher.Invoke is a good way of updating your UI. UI actions have to be performed by the UI Thread, the Dispatcher takes care of this.

You can make your code a bit shorter by using Lambda expressions:

Dispatcher.Invoke((Action<string>) ((data) => { label1.Content = data; }));

I would also suggest putting the proxy in a Using statement, so that would make it:

int result = 0;
using (CalculatorServiceClient proxy = new CalculatorServiceClient()) {
    AsyncCallback callback = new AsyncCallback((asyncResult) => {
     result = proxy.EndAdd(asyncResult);
     Dispatcher.Invoke((Action<string>) ((data) => { label1.Content = data; }));
    });

    proxy.BeginAdd(Convert.ToInt32(txtNumber1.Text), Convert.ToInt32(txtNumber2.Text), callback, null);
}

Or if you like it short:

int result = 0;
using (CalculatorServiceClient proxy = new CalculatorServiceClient())
    proxy.BeginAdd(Convert.ToInt32(txtNumber1.Text), Convert.ToInt32(txtNumber2.Text), new AsyncCallback((asyncResult) => {
     result = proxy.EndAdd(asyncResult);
     Dispatcher.Invoke((Action<string>) ((data) => { label1.Content = data; }));
    }), null);
Zyphrax
Actually, WCF is the one case I know of where you should _not_ implement a `using` block. See "Indisposable: WCF Gotcha #1 - Jesse Ezell Blog" at http://weblogs.asp.net/jezell/archive/2008/07/02/indisposable-wcf-gotcha-1.aspx
John Saunders
Wow, but do I understand correctly that you shouldn't be using the Close method at all, but use the Abort() method? This isn't really a specific Using problem, but more a weird implementation by Microsoft.
Zyphrax
No. Close is ok. The problem is: Exception 1 thrown in "using"; Dispose is called; Dispose calls "Close"; Close throws Exception 2. Exception 1 is lost. The pattern at the blog I posted works. And, yes, it's bad design by Microsoft. They missed one. I found a quote from Don Box saying he was asked if it was ok for Close to throw when called in this scenario, and he figured it was ok, since you were already handling an exception. He missed that we were handling a _different_ exception.
John Saunders
Thanks all for the answers!! Are you guys putting the using statement to call a WCF service within a WPF application? I've got no exceptions being thrown in this scenario. Shall I not put this damn using statement in my code by now?
Junior Mayhé
+1  A: 

Can be done even shorter:

using (var proxy = new CalculatorServiceClient())
{
    proxy.BeginAdd(
         Convert.ToInt32(txtNumber1.Text),
         Convert.ToInt32(txtNumber2.Text),
         (asyncResult) =>
             {
                 int result = proxy.EndAdd(asyncResult);
                 Dispatcher.BeginInvoke(
                   (Action) () => { label1.Content = result; });
             },
         null);
}

The trick is, Invoke/BeginInvoke does not mean you have to pass parameters explicitly, compiler works it out anyway.

And you don't want to use Invoke, because it ties the background thread for no need.

Oleg Mihailik