简体   繁体   中英

How do I deal with this windows phone webclient asynchronous issue?

I have a method as follows:

      public decimal GetExchangeRate(string fromCurrency, string toCurrency)
      {
            GoogleCurrencyService googleCurrencyService = new GoogleCurrencyService();
            return googleCurrencyService.GetRateForCurrency(fromCurrency, toCurrency);

      } 

and another class as follows

public class GoogleCurrencyService
{
    public decimal GetRateForCurrency(string fromCurrency, string toCurrency)
    {

        try
        {
            WebClient client = new WebClient();
            client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(StringDownloadCompleted);
            client.DownloadStringAsync(new Uri(_requestUri + fromCurrency + "=?" + toCurrency));

        }
        catch (Exception)
        {
            ExchangeRate = 0;
        }

        return ExchangeRate;
    }

    private void StringDownloadCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        _response = e.Result;
        ExchangeRate = ParseResponseAndGetExchangeRate();
    }

}//class GoogleCurrencyService

the variable ExchangeRate always come out as zero, so I believe the function call "GetRateForCurrency" returns before the async callback gets called. How do I make sure that does not happen as I need the variable ExchangeRate to be set before being returned. Thanks. Also, I have noticed that the callback never gets called as I have a breakpoint in it and the exception as well which does not get called. So I donot know where the problem is.Any help appreciated.

You can use an event wait handle to block the current thread and wait for the async call...

public class GoogleCurrencyService
{
    private const string RequestUri = "http://www.google.com/ig/calculator?hl=en&q=1{0}%3D%3F{1}";

    public decimal ExchangeRate { get; private set; }

    public decimal GetRateForCurrency(string fromCurrency, string toCurrency)
    {
        ExchangeRate = 0;
        // use a signaler to block this thread and wait for the async call.
        var signaler = new ManualResetEvent(false);
        try
        {
            var client = new WebClient();
            client.DownloadStringCompleted += StringDownloadCompleted;
            // pass the signaler as user token
            client.DownloadStringAsync(new Uri(String.Format(RequestUri, fromCurrency, toCurrency)), signaler);

            // wait for signal, it will be set by StringDownloadCompleted
            signaler.WaitOne();
        }
        finally
        {
            signaler.Dispose();
        }

        return ExchangeRate;
    }

    private void StringDownloadCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        try
        {
            ExchangeRate = ParseResponseAndGetExchangeRate(e.Result);
        }
        finally
        {
            // set signal
            ((ManualResetEvent)e.UserState).Set();
        }
    }

    private decimal ParseResponseAndGetExchangeRate(string result)
    {
        return 123;
    }
}

EDIT: The same class using an async pattern

public class GoogleCurrencyService
{
    private const string RequestUri = "http://www.google.com/ig/calculator?hl=en&q=1{0}%3D%3F{1}";

    public void GetRateForCurrency(string fromCurrency, string toCurrency, Action<decimal> callback)
    {
        var client = new WebClient();
        client.DownloadStringCompleted += StringDownloadCompleted;
        // pass the callback as user token
        client.DownloadStringAsync(new Uri(String.Format(RequestUri, fromCurrency, toCurrency)), callback);
    }

    private void StringDownloadCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        // parse response to get the rate value
        var rate = ParseResponseAndGetExchangeRate(e.Result);

        // if a callback was specified, call it passing the rate.
        var callback = (Action<decimal>)e.UserState;
        if (callback != null)
            callback(rate);
    }

    private decimal ParseResponseAndGetExchangeRate(string result)
    {
        return 123;
    }
}

Consuming the async class:

// this is your UI form/control/whatever
public class MyUI
{
    public void OnButtonToGetRateClick()
    {
        var from = "USD"; // or read from textbox...
        var to = "EUR";

        // call the rate service
        var service = new GoogleCurrencyService();
        service.GetRateForCurrency(from, to, (rate) =>
            {
                // do stuff here to update UI.
                // like update ui.
            });
    }
}

Maybe you'll have to dispatch the UI changes to ui thread. I not have WP framework here to confirm that this is the case, but I think it is.

when you run an async method, you will get result in completed method that is

StringDownloadCompleted

so in your code you call async method and return immediatly ExchangeRate which will always be 0.

you have to get ExchangeRate in your completed method StringDownloadCompleted

if you want to get ExchangeRate in your GetRateForCurrency make a synchronous call

client.DownloadString(new Uri(_requestUri + fromCurrency + "=?" + toCurrency));

here's what you need to do.

you create an eventin the class. in your code, you fire the async web client call. Once the call is complete, you wrap the data and set the event. I tend to define event args which can hold the data.

once you set the event the caller will get notified.

if you want an example, have a look at the source in my post here http://invokeit.wordpress.com/2012/06/30/bing-mapcontrol-offline-tiles-solution-wpdev-wp7dev/

its an extension of bing maps sample and it contains address finder class. have a look at how it is fired and how client gets notified

ok wait after call uri

public decimal GetRateForCurrency(string fromCurrency, string toCurrency) {

    try
    {
        WebClient client = new WebClient();
        client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(StringDownloadCompleted);
        client.DownloadStringAsync(new Uri(_requestUri + fromCurrency + "=?" + toCurrency));
        Thread.sleep(500000); //waiting
    }
    catch (Exception)
    {
        ExchangeRate = 0;
    }

    return ExchangeRate;
}

so set a webcontrol like label and do this

 private void StringDownloadCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    _response = e.Result;
    yourlabel.Text = _response ;
    ExchangeRate = ParseResponseAndGetExchangeRate();
}

Your only choice here to make GetRateForCurrency asynchronous too (which means raising its own Completed event).

Is the TPL were supported, you could use Task<T> as a nice way to wrap the asynchony down the chain, but unfortunately it's not supported by WP7.

As an alternative, and what I've done, is to use the Reactive Extensions ( Microsoft.Phone.Reactive ) and pass an IObservable down the chain - however, Rx is a lot to learn if you're only going ot use it for this one scenario.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM