user818700
user818700

Reputation:

Changing from Synchronous mindset to Asynchronous

I'm busy with a windows phone application that of course uses silverlight. This means that calling any webservices has to be done asynchronously, and since this is all good and well in regards to best practice in preventing your entire app in hanging when waiting for a resource, I'm still stuck in the "synchronous mindset"...

Because the way I see it now is that you end up having 2 methods that needs to handle one function, e.g:

1)The method that actually calls the webservice:

public void myAsyncWebService(DownloadStringCompletedEventHandler callback)
{
    //Url to webservice
    string servletUrl = "https://deangrobler.com/someService/etc/etc"

    //Calls Servlet
    WebClient client = new WebClient();
    client.DownloadStringCompleted += callback;
    client.DownloadStringAsync(new Uri(servletUrl, UriKind.Absolute));
}

2) and the method that handles the data when it eventually comes back:

private void serviceReturn(object sender, DownloadStringCompletedEventArgs e)
{
    var jsonResponse = e.Result;
    //and so on and so forth...
}

So instead of having to just create and call a single method that goes to the webservice, gets the returned result and sent it back to me like this:

public string mySyncWebService(){
    //Calls the webservice
    // ...waits for return
    //And returns result
}

I have to in a Class call myAsyncWebService, AND create another method in the calling class that will handle the result returned by myAsyncWebService. Just, in my opinion, creates messy code. With synchronous calls you could just call one method and be done with it.

Am I just using Asynchronous calls wrong? Is my understanding wrong? I need some enlightment here, I hate doing this messy-async calls. It makes my code too complex and readability just goes to... hell.

Thanks for anyone willing to shift my mind!

Upvotes: 0

Views: 1039

Answers (5)

Martin Liversage
Martin Liversage

Reputation: 106826

With synchronous calls you could just call one method and be done with it.

Sure, but if you do that from the UI thread you will block the entire UI. That is unacceptable in any modern application, in particular in Silverlight applications running in the browser or in the phone. A phone that is unresponsive for 30 seconds while a DNS lookup times out is not something anybody wants to use.

So on the UI thread, probably because the user did some action in the UI, you start an asynchronous call. When the call completes a method is called on a background thread to handle the result of the call. This method will most likely update the UI with the result of the asynchronous call.

With the introduction of async and await in .NET 4.5 some of this "split" code can be simplified. Luckily async and await is now available for Windows Phone 7.5 in a beta version using the NuGet package Microsoft.Bcl.Async.

Here is a small (and somewhat silly) example demonstrating how you can chain two web service calls using async. This works with .NET 4.5 but using the NuGet package linked above you should be able to do something similar on Windows Phone 7.5.

async Task<String> GetCurrencyCode() {
  using (var webClient = new WebClient()) {
    var xml = await webClient.DownloadStringTaskAsync("http://freegeoip.net/xml/");
    var xElement = XElement.Parse(xml);
    var countryName = (String) xElement.Element("CountryName");
    return await GetCurrencyCodeForCountry(countryName);
  }
}

async Task<String> GetCurrencyCodeForCountry(String countryName) {
  using (var webClient = new WebClient()) {
    var outerXml = await webClient.DownloadStringTaskAsync("http://www.webservicex.net/country.asmx/GetCurrencyByCountry?CountryName=" + countryName);
    var outerXElement = XElement.Parse(outerXml);
    var innerXml = (String) outerXElement;
    var innerXElement = XElement.Parse(innerXml);
    var currencyCode = (String) innerXElement.Element("Table").Element("CurrencyCode");
    return currencyCode;
  }
}

However, you still need to bridge between the UI thread and the async GetCurrencyCode. You can't await in an event handler but you can use Task.ContinueWith on the task returned by the async call:

void OnUserAction() {
  GetCurrencyCode().ContinueWith(GetCurrencyCodeCallback);
}

void GetCurrencyCodeCallback(Task<String> task) {
  if (!task.IsFaulted)
    Console.WriteLine(task.Result);
  else
    Console.WriteLine(task.Exception);
}

Upvotes: 1

Felice Pollano
Felice Pollano

Reputation: 33252

To avoid creating messy code, if you can't use the async / await pattern because you are on older framework, you will find helpful check CoRoutines in their Caliburn Micro implemantation. With this pattern you create an enumerable yielding at each turn a new asynchronous segment to execute: by the reader point of view asynchronous steps appear as a sequence, but walking among the steps ( so yielding the next one ) is done externally by asynchronously wait the single task. It is a nice pattern easy to implement and really clear to read. BTW if you don't want to use Caliburn Micro as your MVVM tool because you are using something else, you can use just the coroutine facility, it is very insulated inside the framework.

Let me just post some code from an example in this blog post.

public IEnumerable<IResult> Login(string username, string password)
{
    _credential.Username = username;
    _credential.Password = password;

    var result = new Result();
    var request = new GetUserSettings(username);

    yield return new ProcessQuery(request, result, "Logging In...");

    if (result.HasErrors)
    {
        yield return new ShowMessageBox("The username or password provided is incorrect.", "Access Denied");
        yield break;
    }

    var response = result.GetResponse(request);

    if(response.Permissions == null || response.Permissions.Count < 1)
    {
        yield return new ShowMessageBox("You do not have permission to access the dashboard.", "Access Denied");
        yield break;
    }

    _context.Permissions = response.Permissions;

    yield return new OpenWith<IShell, IDashboard>();
}

Isn't it easy to read? But it is is actually asynchronous: each yield steps are executed in an asynchronous manner and the execution flow again after the yield statement as soon the previous task completed.

Upvotes: 1

Stephen Cleary
Stephen Cleary

Reputation: 456467

You have to turn your mind inside out to program asynchronously. I speak from experience. :)

Am I just using Asynchronous calls wrong? Is my understanding wrong?

No. Asynchronous code is fairly difficult to write (don't forget error handling) and extremely difficult to maintain.

This is the reason that async and await were invented.

If you're able to upgrade to VS2012, then you can use Microsoft.Bcl.Async (currently in beta) to write your code like this:

string url1 = "https://deangrobler.com/someService/etc/etc";
string jsonResponse1 = await new WebClient().DownloadStringTaskAsync(url1);

string url2 = GetUriFromJson(jsonResponse1);
string jsonResponse2 = await new WebClient().DownloadStringTaskAsync(url2);

Easy to write. Easy to maintain.

Upvotes: 2

Henrik S&#246;derlund
Henrik S&#246;derlund

Reputation: 4328

It all becomes much easier and readable if you use lambdas instead. This also enables you to access variables declared in the "parent" method, like in the following example:

private void CallWebService()
{
    //Defined outside the callback
    var someFlag = true;

    var client = new WebClient();
    client.DownloadStringCompleted += (s, e) =>
    {
        //Using lambdas, we can access variables defined outside the callback
        if (someFlag)
        {
            //Do stuff with the result. 
        }
    };

    client.DownloadStringAsync(new Uri("http://www.microsoft.com/"));
}


EDIT: Here is another example with two chained service calls. It still isn't very pretty, but imho it is a little more readable than the OPs original code.

private void CallTwoWebServices()
{
    var client = new WebClient();
    client.DownloadStringCompleted += (s, e) =>
    {
        //1st call completed. Now make 2nd call.
        var client2 = new WebClient();
        client2.DownloadStringCompleted += (s2, e2) =>
        {
            //Both calls completed.
        };
        client2.DownloadStringAsync(new Uri("http://www.google.com/"));
    };
    client.DownloadStringAsync(new Uri("http://www.microsoft.com/"));
}

Upvotes: 1

Kevin
Kevin

Reputation: 562

Async is like when you make a telephone call and get an answering machine, if you want a return call you leave your number. The first method is your call asking for data, the second is the "number" you've left for the return call.

Upvotes: 1

Related Questions